Card 03/ 07
Mutable State in a Singleton Bean
A counter that looks private to each caller is shared by everyone, and under load it also races.
A singleton bean is shared by every caller, so any field it changes is changed for everyone. A counter or a list kept in a singleton's field looks private to each caller in the code, and it is not.
RideCounter looks as if it gives each caller a running total of their own. There is exactly one RideCounter, and every caller shares it:
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());request A records ride #1
request B records ride #2Request B did not get a counter starting at zero; it got request A's counter, already at one. That is not a bug in the container, because it is exactly what a singleton promises. The bug is treating a shared, changing field as if each caller had a private copy. Under real concurrent traffic it gets worse: two threads (separate lines of execution, each handling a request) incrementing the same field at once can lose updates, which is called a race condition.
Keep a singleton's fields unchanged after startup, and move changing data somewhere scoped to whoever is asking: a local variable, a method parameter, or a bean with a shorter scope (a scope decides how long one instance of a bean lives and who shares it). If a count really must be shared, use a thread-safe type such as AtomicInteger.
← → move through sections and cards