Concurrency

Memory visibility and volatile: interview questions and how to answer them

`volatile` guarantees that a write is visible to every subsequent read and that accesses are not reordered around it — visibility and ordering, never atomicity of a compound operation.

Written and reviewed by Sahil Srivastav

ConcurrencyJava memory modelSenior-level

What it actually is

Visibility is the question of whether a write performed by one thread can be observed by another, and the surprising answer is that without synchronisation there is no guarantee at all — not "eventually", not "after a while". The Java Memory Model permits a thread to keep a value in a register or a store buffer indefinitely, and permits the compiler to hoist a field read out of a loop entirely. A non-volatile `boolean stopped` read in a tight loop can legally be compiled to an infinite loop, and HotSpot does exactly that after JIT compilation.

Marking a field `volatile` does two things. Reads and writes go to and from memory in a way other threads can observe, so no thread caches the value in a register across accesses. And it establishes ordering: everything a thread did before a volatile write is visible to any thread that reads that same volatile and sees the new value. That second part is the important one and the part candidates miss — `volatile` publishes not just the field but everything written before it.

What it does not give you is atomicity of read-modify-write. `volatile int n; n++` loads, adds, and stores, and two threads can interleave inside that. Nor is it a lock: it makes each individual access indivisible for 32-bit and reference types, and it additionally makes `long` and `double` reads and writes atomic on platforms where they otherwise might tear — but it never makes a sequence of operations atomic.

Why it matters in production

Because the most common shape of the bug is a shutdown flag that does not shut anything down. A worker loop polls a non-volatile boolean set by another thread; the loop runs a few thousand iterations, the JIT compiles it, the field read is hoisted, and the thread never exits. The application hangs on shutdown and nobody can see why, because the code is obviously correct on inspection.

Because it decides whether lazy initialisation works. A field written by a constructor and published through a non-volatile reference can be observed by another thread as a non-null reference pointing at an object whose fields are still at their defaults. That is not theoretical — it is the exact reason the classic double-checked locking idiom was broken before Java 5 and requires `volatile` today.

And because interviewers use `volatile` as a one-question filter for whether someone has read about the memory model or only about the syntax. The answer "volatile makes it thread-safe" fails; the answer "volatile gives visibility and ordering, and this operation additionally needs atomicity, so I want an atomic or a lock" passes.

How it works

The optimisation, not the hardware, is usually the culprit

People explain stale reads with CPU caches, and cache coherence is real, but on x86 the more frequent cause is the JIT: hoisting a loop-invariant field read into a register before the loop. The field never changes as far as the compiler can prove, so it reads it once. `volatile` removes that licence, which is why the fix works even on a strongly ordered architecture where cache coherence would have delivered the value anyway.

A volatile write publishes everything before it

The ordering rule is: a write to a volatile field happens-before every subsequent read of that field. Combined with program order, that means all writes a thread made before the volatile write are visible to a reader that sees it. This is the mechanism behind safe publication — set up the object, then store the reference into a volatile field last.

Reordering is legal until you forbid it

Both compiler and processor may reorder independent memory operations. Writing `config = new Config(); ready = true;` gives no guarantee that a reader seeing `ready == true` will see a complete `config`, unless `ready` is volatile — the volatile store acts as a release barrier that prevents the earlier write from sliding after it.

Volatile is cheap to read, not free to write

A volatile read on x86 costs essentially nothing beyond forbidding caching in a register. A volatile write compiles to a store followed by a barrier, which prevents the store buffer from reordering and costs on the order of tens of cycles. That asymmetry is why read-mostly flags are an ideal use and a hot volatile counter is not.

Locks give visibility too, and more

Releasing a monitor flushes and an acquire makes visible, so the same ordering guarantee comes from any properly used lock — plus mutual exclusion. `volatile` is the right choice precisely when you need the visibility and do not need exclusion: a flag, a published immutable reference, a monotonically updated value that a single thread writes.

Implementing it

Use `volatile` for a flag written by one thread and read by others, and for the reference through which you publish an immutable object. Those two cases cover the large majority of legitimate uses.

Use an `Atomic*` class the moment the operation reads and then writes based on what it read — counters, accumulators, compare-and-set state machines. `volatile` plus `++` is the canonical wrong answer.

For a stoppable worker, prefer interruption to a flag where you can: `Thread.interrupt` plus checking `isInterrupted` also unblocks a thread parked in `sleep`, `wait`, or a blocking queue, which a flag cannot do.

Do not reason about visibility from test results. A missing `volatile` frequently works on x86 and fails on ARM, and works interpreted and fails after JIT compilation — so a green test on your laptop says nothing about correctness.

// Legal for the JIT to hoist the read: this loop may never exit
private boolean stopped = false;
public void run()  { while (!stopped) doWork(); }
public void stop() { stopped = true; }

// volatile removes the licence to cache the read
private volatile boolean stopped = false;

// Unsafe publication: reader can see a non-null ref to a half-built object
private Config config;
private volatile boolean ready;
void init()  { config = load(); ready = true; }   // volatile write publishes config
Config get() { return ready ? config : null; }    // volatile read sees it complete

// volatile does NOT make this atomic — three steps, interleavable
private volatile int hits;
void hit() { hits++; }                            // still loses increments
private final AtomicInteger hits = new AtomicInteger();
void hit() { hits.incrementAndGet(); }            // CAS loop: atomic

Interview questions and how to answer them

A worker thread loops on a boolean flag and never stops when another thread sets it. Why?

Because nothing in the program orders the write against the read, so the JVM is free to read the field once and keep it in a register for the life of the loop — and HotSpot does exactly that once the loop is JIT-compiled, which is why it often runs correctly for the first few thousand iterations. Declaring the flag `volatile` forbids the caching and establishes happens-before, so the write becomes visible. For a real worker I would use interruption instead, because a flag cannot wake a thread blocked in `take()` or `sleep()`.

What exactly does `volatile` guarantee?

Two things. Visibility: a read of a volatile field always returns the most recent write, and the value cannot be cached across accesses. Ordering: a volatile write happens-before any subsequent read of that field, so everything the writer did before the write is visible to a reader that sees it — that is the release/acquire pair, and it is what makes safe publication work. It additionally makes `long` and `double` accesses atomic. It does not make compound operations atomic and it provides no mutual exclusion.

Why does double-checked locking need `volatile`?

The outer check reads the field without holding the lock. Without `volatile`, the thread that built the instance inside the lock has no ordering constraint between writing the object's fields and writing the reference, so a second thread can see a non-null reference to an object whose fields are still at their defaults and use it. `volatile` on the instance field makes the write a release, so any thread seeing the reference sees the fully constructed object. On the JVM I would normally avoid the idiom entirely and use a holder class, where class initialisation gives the same guarantee for free.

`volatile` versus `AtomicInteger` — when does each apply?

`volatile` when a value is written by one party and read by others and the write does not depend on the previous value: a flag, a published reference, a last-updated timestamp. `AtomicInteger` when the new value is computed from the old one, because that is a read-modify-write and needs the load and store to be indivisible — which is what `compareAndSet` provides, and what `incrementAndGet` is built on. If the counter is extremely hot, `LongAdder` beats `AtomicInteger` by sharding the cells and avoiding CAS contention.

Does `synchronized` give you visibility, or only exclusion?

Both. Releasing a monitor is a release and acquiring it is an acquire, so everything a thread wrote before leaving a synchronized block is visible to the next thread that enters a block on the same monitor. That is why code that correctly guards all access with one lock needs no `volatile` — and also why partially synchronising, writes only, gives readers no guarantee at all.

Answers that lose the round

  • Saying `volatile` makes an operation atomic, or using it on a counter that is incremented
  • Explaining the stale-read bug purely as CPU caching, and being unable to account for it on x86 where coherence is automatic — the JIT hoist is the missing piece
  • Believing a non-volatile write becomes visible "eventually"; the model provides no such guarantee and the JIT may remove the read entirely
  • Adding `volatile` to an object reference and assuming the object's mutable fields are now safely shared — the guarantee covers the reference, not the contents
  • Using `volatile` for a two-field invariant, where you need both fields to be seen consistently and only a lock or an immutable snapshot provides that
  • Omitting `volatile` from the double-checked locking instance field, which is the specific defect that made the pre-Java-5 idiom broken
  • Claiming `synchronized` is only about mutual exclusion and does not affect visibility

Practise memory visibility and volatile in a real repository

Gronex ships this as a runnable repository: a hand-rolled queue whose consumers miss signals and stall. The tests exercise the exact interleaving where the state change and the notification are not ordered, so a sprinkling of `volatile` does not pass — the wait predicate has to be correct.

FAQ

Is `volatile` in Java the same as in C?

No, and confusing them is a common error. In C and C++, `volatile` means "do not optimise away this access" and is for memory-mapped hardware registers; it provides no inter-thread ordering. Java's `volatile` since JSR-133 is a full memory-model construct with release/acquire semantics. The C++ equivalent of Java `volatile` is `std::atomic` with the default sequentially consistent ordering.

Do final fields need volatile?

No. The JMM gives `final` fields a freeze at the end of the constructor: a thread that obtains a reference to a properly constructed object is guaranteed to see its final fields initialised, provided the reference did not escape during construction. That last clause is the catch — publishing `this` from a constructor voids the guarantee.

Does volatile hurt performance?

Reads are close to free on x86 — the cost is losing the option to cache the value in a register. Writes carry a store barrier, so a volatile field written in a hot loop is measurably slower than a plain one, and a volatile field sharing a cache line with another hot field costs more again through false sharing. For flags and published references, the cost is irrelevant.

Related

More backend concepts