Card 03/ 07

GotchaDifficulty: Intermediate1 min

The Counter That Only Breaks Under Load

RideCounter looks like it gives each caller their own running total. It doesn't — there is exactly one RideCounter, and every caller shares it.

java
class RideCounter {
    private int total = 0;
    int recordRide() { return ++total; }
}

// two unrelated callers, nowhere near each other in the codebase
RideCounter forRequestA = context.getBean(RideCounter.class);
System.out.println("request A records ride #" + forRequestA.recordRide());

RideCounter forRequestB = context.getBean(RideCounter.class);
System.out.println("request B records ride #" + forRequestB.recordRide());
text
request A records ride #1
request B records ride #2
run in a container — same instance, running total

Request B did not get its own counter starting at zero — it got request A's counter, already at one. That is not a bug in the container; it is exactly what a singleton promises. The bug is treating a shared, mutable field as if the container were somehow giving each caller a private copy — it is not, and under real concurrent traffic the same field being incremented from multiple threads at once is a data race on top of the sharing, not a separate problem.

The remedy: keep singleton state immutable, or move the mutable part somewhere scoped to whoever's asking — a local variable, a parameter, or a bean scoped shorter than singleton.