volatile & Happens-Before
volatile ensures that reads and writes to a variable are always done directly to main memory, not to thread-local CPU caches. It prevents visibility bugs where one thread's writes are not seen by another.
volatile = a town crier who shouts every update to the whole village. Everyone hears the news (visibility), but two criers can still shout at the same time and garble the message (no atomicity).
volatile establishes a happens-before relationship: a write to a volatile variable happens-before every subsequent read of that same variable. This also prevents certain instruction reorderings by the JIT and CPU. volatile does NOT provide atomicity for compound operations — volatile long/double reads and writes are guaranteed atomic by the JMM (Java 5+) on all JVMs regardless of word size, but i++ on volatile int is still a three-step read-modify-write that can race. Use cases: flags (boolean running = true), double-checked locking (the instance field must be volatile), publishing immutable objects.
The JMM (Java Memory Model, JSR-133, Java 5+) defines happens-before as a partial order on memory operations. volatile writes/reads are a subset of synchronization actions that impose total order among themselves and create happens-before edges. The model is defined in terms of actions and orders, not in terms of caches — it specifies what values a read is allowed to see, not the mechanism. CPU memory barriers (mfence on x86, dmb on ARM) are the hardware implementation. StampedLock's optimistic reads avoid volatile reads for the common case — readers check a stamp and retry if a write occurred.
volatile guarantees visibility (writes are flushed to main memory, reads bypass CPU cache) and prevents specific instruction reorderings, establishing a happens-before relationship between a write and subsequent reads of that variable. It does NOT provide atomicity — use AtomicInteger or synchronized for compound read-modify-write operations. The canonical use case is a boolean stop flag: one thread sets volatile boolean stopped = true; the worker thread's while (!stopped) loop will eventually see it without synchronization.
double-checked locking without volatile is broken — without it, the JVM can reorder the constructor call and the reference assignment, allowing another thread to see a non-null but partially constructed object.