Java volatile and happens-before: Visibility, ordering, and the limits of atomicity
Baseline: Java 17+. This article discusses the Java Memory Model without tying the discussion to any particular CPU architecture.
1. What volatile guarantees
A write to a volatile variable establishes a happens-before relationship with a subsequent read of the same variable by another thread. It provides visibility and some ordering guarantees, but it does not make a sequence of operations atomic.
A volatile boolean stopped can be used to publish a stop flag. A volatile int count cannot safely support count++, because the increment consists of three actions: reading, adding one, and writing back.
2. Choosing the right tool
Use AtomicInteger or AtomicReference for atomic updates to a single variable; use locks, concurrent containers, or CAS loops for compound state transitions; use a volatile reference to publish immutable configuration; and consider VarHandle only when you need low-level memory ordering.
3. Sources of happens-before
Thread startup, thread termination, lock release and acquisition, Future completion, and specified operations on concurrent containers can also establish visibility relationships. Don't use “preventing instruction reordering” as a substitute for the JMM rules.
4. Verification
Use jcstress for complex memory ordering, JMH for performance comparisons, and concurrency tests that cover contention and failure injection for writes to business state. volatile is neither a lock nor a guarantee of atomicity for compound operations.