What does the volatile keyword guarantee in Java, and what does it deliberately not guarantee?
Marking a field volatile gives you two guarantees: visibility and ordering. Any write to a volatile field happens-before any subsequent read of it by any thread. And the accesses to volatile fields are not reordered with respect to each other or with respect to other happens-before edges, so you never see a stale value that was written after the one you read.
What volatile deliberately does not give you is atomicity. The increment operation count++ is read, add and write — three separate steps — and volatile does not make that sequence atomic. Two threads incrementing the same volatile counter can still lose updates. The classic demonstration is a counter that ends up below the expected total even though every field access is volatile.
So the rule is: use volatile for a flag or a single value that one thread writes and another reads, where the value itself is what matters. Use an atomic class or a lock when you need a read-modify-write to be atomic.
public class Main {
public static void main(String[] args) throws InterruptedException {
// A volatile flag: the writer's value becomes visible promptly.
Stopwatch stopwatch = new Stopwatch();
Thread reader = new Thread(() -> {
while (!stopwatch.flag) {
Thread.onSpinWait();
}
System.out.println("reader saw the flag: " + stopwatch.flag);
});
reader.start();
Thread.sleep(20);
stopwatch.flag = true; // the reader is guaranteed to see this
reader.join(1000);
System.out.println("reader state: " + reader.getState());
}
}
class Stopwatch {
volatile boolean flag = false;
}The output shows the reader waking up and reporting the flag. The volatile write in the main thread happens-before the volatile read in the reader, so the reader cannot miss the change and cannot spin forever.
Two more points that come up in interviews. Volatile does not give you the compare-and-swap semantics you need for a lock-free update — that is what AtomicInteger.compareAndSet provides. And volatile gives no protection against a compound operation: reading two volatile fields and acting on them is still not atomic as a whole, because another thread can change one of them in between.