Memra

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.

NORMAL ~/memra/learn/comp-308/volatile-atomics-visibility utf-8 LF