Races & Visibility
intermediate15 min readLesson 95 of 180
Lost updates, invisible writes, and the volatile → atomic → synchronized ladder with when each is correct.
Race conditions and visibility
Two problems plague shared mutable state:
Interference — count++ is read-modify-write; two threads can read
the same value and lose an increment:
class Counter { int count; void inc() { count++; } }
// 2 threads × 10_000 increments → often < 20_000
Visibility — without synchronization, one thread's writes may never become visible to another (the JIT may hoist the read out of a loop):
boolean stop = false; // plain field — may never be seen!
// Thread A: while (!stop) work();
// Thread B: stop = true;
Fixes, weakest to strongest:
volatile— visibility only; fine for flags, wrong for count++AtomicLong/AtomicInteger— atomic read-modify-write (incrementAndGet())synchronizedblocks — mutual exclusion for compound actions
class SafeCounter {
private final AtomicLong count = new AtomicLong();
void inc() { count.incrementAndGet(); }
long value() { return count.get(); }
}
Rule of thumb: make shared state immutable, or confine it to one thread, or protect every access consistently — pick one and document it.