W3docs

Java volatile Keyword

Use the volatile keyword in Java to give field reads and writes visibility and ordering guarantees across threads — without taking a lock.

Java volatile Keyword

synchronized solves two problems at once: mutual exclusion and memory visibility. Sometimes you only need the second. A single boolean flag that one thread sets and another thread polls doesn't need exclusive access — there's only one writer and one or many readers, and the work itself doesn't compose. Locking it is overkill; not locking it is broken. volatile is the keyword that gives you visibility without exclusion.

It's a narrow tool. Get the use case right and it's the fastest thread-safe primitive Java has. Get it wrong — try to use it for compound updates like ++ — and you'll have the same bugs synchronized was supposed to fix.

The problem volatile exists to solve

Without any synchronisation:

class Worker implements Runnable {
  boolean stop = false;                                 // plain field
  public void run() {
    while (!stop) {                                     // hot loop
      doWork();
    }
  }
}

Worker w = new Worker();
new Thread(w).start();
Thread.sleep(1000);
w.stop = true;                                          // ask it to stop

This program may never stop. The reason: nothing in the JVM is required to flush stop from one thread's CPU cache to main memory, and nothing is required to invalidate the worker's cached copy when the main thread writes. The JIT is also allowed to hoist !stop out of the loop entirely — the compiler sees that the body doesn't modify stop, so it caches the value in a register. Forever.

This is the visibility bug. The fix:

volatile boolean stop = false;

Now every write to stop flushes to main memory and every read pulls from main memory. The JIT can't hoist the read; the loop sees the new value within microseconds of the writer's update.

What volatile guarantees

Four things:

  1. Visibility. A write to a volatile field is guaranteed to be visible to subsequent reads in any other thread.
  2. Atomicity of read and write — but only for the field itself. A volatile long and volatile double read/write is atomic; without volatile, a 64-bit read/write may be torn on a 32-bit JVM.
  3. Ordering (happens-before). Everything that happened before a volatile write in the writing thread is visible to anything that happens after the matching volatile read in the reading thread. This is the part you don't see in the syntax but is the most powerful guarantee.
  4. No reordering across the access. The compiler and CPU can't move ordinary reads/writes past a volatile access in either direction.

The third one — happens-before via a volatile write/read pair — is sometimes called the cheapest publication primitive. If you write a field, then a volatile boolean ready = true, another thread that sees ready == true is guaranteed to see the earlier write too. That's how a lot of thread-safe initialisation gets done without a lock.

What volatile does not guarantee

The most common mistake:

volatile int counter = 0;

void increment() { counter++; }                       // STILL BROKEN

volatile makes the read atomic and the write atomic — but counter++ is read-modify-write, which is three operations. Two threads both read 42, both compute 43, both write 43. One increment is still lost.

For compound updates, volatile is not enough. Use synchronized, a Lock, or — almost always the right answer — an AtomicInteger.

The other thing volatile doesn't do:

  • It doesn't give mutual exclusion. Two threads can write to a volatile field simultaneously; the result is "one of them wins, the other is lost," which is the right answer for flag-like state and the wrong answer for "merge these two values."
  • It doesn't synchronise multiple fields atomically together. If your invariant is "if A is true then B must be 42," writing each one to a separate volatile field can leave another thread seeing the new A and the old B.

The publication pattern

The most useful application of volatile outside the stop-flag case:

class LazyResource {
  private Resource cached;                            // not volatile
  private volatile boolean ready = false;             // the publication flag

  public Resource get() {
    if (!ready) {
      synchronized (this) {
        if (!ready) {
          cached = buildResource();                   // expensive init
          ready = true;                               // publishes cached
        }
      }
    }
    return cached;
  }
}

The volatile write of ready = true publishes the prior write to cached. Any thread that subsequently reads ready == true is guaranteed to see the fully-initialised cached. The lock is only contended on the first call; subsequent calls just read ready and skip the synchronisation entirely. This is the classic double-checked locking idiom, made correct by volatile.

Without volatile on ready, the optimisation is broken — the second thread can see ready == true and then read cached as null. With volatile, it can't.

(The modern alternative is just to do private final Resource cached = buildResource(); if eager init is fine, or Supplier<Resource> lazy = Suppliers.memoize(...) from your library of choice. Hand-rolled double-checked locking is rare in modern code; the pattern is worth knowing because you'll read it.)

When volatile is exactly right

A small handful of use cases where volatile is the best answer:

  • A single-writer status flag read by many threads. Stop signals, "ready" flags, configuration version numbers.
  • A reference to an immutable value that gets atomically swapped. Cache that the writer rebuilds and publishes; readers see the old or new whole object, never a half-built one.
  • Publishing the result of one-time initialisation through the double-checked-locking pattern.
  • A timestamp or sequence number that one thread sets and others read — where missing the very latest update is acceptable but the value must always be self-consistent.

Outside those cases, you usually want a higher-level tool. Don't be clever with volatile — its semantics are too thin to support general state.

volatile long and volatile double on 32-bit JVMs

A historical wrinkle. On a 32-bit JVM, a non-volatile long or double read/write is allowed to be split into two 32-bit accesses. Two threads can produce a torn value — the high 32 bits of one write and the low 32 bits of another. volatile long and volatile double are guaranteed atomic regardless of word size.

Modern JVMs almost always run 64-bit, and 64-bit JVMs make all primitives atomic anyway, but the rule is still in the spec. If you have a long/double shared across threads, mark it volatile (or use AtomicLong/AtomicDouble).

A worked example: the stop-flag bug

The program below runs the worker with a plain boolean first (which usually never stops), then with a volatile boolean (which stops promptly). A watchdog cuts the broken case off after 2 seconds so the demo terminates.

java— editable, runs on the server

What to take from the run:

  • The PLAIN worker often did not stop within the watchdog window. The main thread wrote stop = true; the worker thread, with its register-cached copy of stop, never noticed. Add volatile and the bug disappears. This is the visibility problem — every multithreaded program has a version of it.
  • The VOLATILE worker stopped within a microsecond or two of the write. That's the cost of the memory barrier the JVM emits for a volatile write/read pair — single-digit microseconds at worst, and often lower. Cheap for the right use case.
  • The VOLATILE++ counter was less than 200,000. volatile does not turn n++ into an atomic operation — it's still read-modify-write, and two threads can both read 42 and both store 43. This is the most common misuse of volatile. For compound updates, reach for AtomicInteger (next chapter).
  • The cost of volatile is small but real — every read and every write touches main memory and emits a memory barrier. For a flag that's read once per loop iteration, that's nothing. For a field that's read in a tight inner loop, the barrier cost adds up. If you find a hot volatile in a profile, consider whether the read can be hoisted to a local and the loop body run on that snapshot.
  • volatile is also the publication mechanism for a reference — write the data, then volatile-write the reference. The reading thread that sees the new reference is guaranteed to see all the data the writer prepared. That's the building block of the double-checked-locking and the immutable-value-swap patterns.

What's next

The next chapter, Java Atomic Variables, introduces AtomicInteger, AtomicLong, AtomicReference, and the rest of the java.util.concurrent.atomic family — the right tool for "increment a counter from many threads" and any other lock-free compound update.

Practice

Practice

You declare `private volatile int counter = 0;` and call `counter++` from multiple threads. After 4 threads each do 100,000 increments, what value does `counter` typically hold?