volatile, atomics, and visibility
The visibility problem, when volatile fixes it (and when it does not), and lock-free counters with AtomicInteger.
Visibility, not just atomicity
synchronized fixes two problems at once: it gives mutual exclusion *and* it makes one thread's writes visible to others. The visibility half is easy to forget. Without any memory-coordination, a thread may keep a stale cached copy of a field in a register and never see another thread's update — even though no race in the read-modify-write sense ever occurs.
A notorious example is a stop flag:
class Worker implements Runnable {
private boolean running = true; // BUG: not visible across threads
public void run() {
while (running) { /* spin doing work */ }
System.out.println("stopped");
}
public void stop() { running = false; }
}
Another thread calls stop(), but the worker thread may *never* see running become false and can loop forever, because the JVM is free to cache running in a register inside the loop.
volatile: a visibility guarantee
Declaring the field volatile fixes exactly this: every read of a volatile field sees the most recent write by any thread, and writes are not reordered around it. It establishes a *happens-before* relationship between a write and subsequent reads.
private volatile boolean running = true;
Now stop() reliably ends the loop. But volatile does NOT make compound actions atomic. volatile int n; n++; is still a read-modify-write race — volatile guarantees you see the latest value, not that your update wins. Use volatile only for simple flags and single-writer publish patterns, never for counters incremented by many threads.
Atomics: lock-free read-modify-write
For a counter shared across threads, java.util.concurrent.atomic.AtomicInteger gives an atomic increment without an explicit lock, using a hardware compare-and-swap:
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); } // atomic
public int get() { return count.get(); }
}
incrementAndGet() is a single atomic step, so 1,000 increments from each of two threads yields exactly 2,000 — no synchronized, no lost updates. The atomic classes (AtomicInteger, AtomicLong, AtomicReference) are the right tool when the *only* shared operation is a simple counter or reference swap.