A learning path ready to make your own.

Java Volatile

TL;DR volatile is a lightweight Java concurrency primitive that guarantees visibility and ordering (release/acquire) but not mutual exclusion. A write to a volatile variable happens-before every subsequent read of that same variable, ensuring the reader sees the write and prior memory actions. Use volatile for simple flags, safe publication of immutable objects, correct double-checked locking (with volatile), and some lock-free patterns. Do not use it for compound read-modify-write operations — use synchronized or java.util.concurrent.atomic (CAS) instead. Since Java 5 (JSR-133) volatile semantics are well-defined; Java 9+ adds VarHandle for finer-grained memory ordering (acquire/release/opaque). Core semantics Visibility: A volatile write by one thread is visible to subsequent volatile reads by other threads. Ordering (release/acquire): A volatile write has release semantics (prior writes cannot be reordered after it). A volatile read has acquire semantics (subsequent reads/writes cannot be reordered before it). Atomicity: Individual reads/writes of volatile variables are atomic for all primitive types (including long/double) and references. Limitations: volatile does not provide mutual exclusion or atomicity for compound operations (e.g., counter++). Happens-before & ordering (summary) Key JMM concepts: program order, monitor order, volatile order, and the happens-before relation. A volatile write happens-before subsequent volatile reads of that variable; happens-before is transitive. These constraints prevent certain reorderings and ensure visibility of memory actions that precede a volatile write. Practical uses and idioms Stop flag: single-writer, many-reader boolean flags (worker loop termination). Safe publication: publish a fully-constructed immutable object by writing its reference to a volatile field. Double-checked locking: correct when the singleton reference is volatile (prevents seeing a partially constructed object). Sequence/stamp patterns: version counters used with acquire/release ordering to detect concurrent modification (optimistic reads). Not suitable: counters using ++ or other read-modify-write sequences — use AtomicInteger, LongAdder, or synchronized blocks. Examples (condensed) Stop flag: private volatile boolean running = true; void stop() { running = false; } Safe publication of immutable object: private static volatile Config cfg; void set(Config c) { cfg = c; } // publishes fully-initialized cfg Config get() { return cfg; } Volatile vs synchronized vs Atomic* volatile: cheap visibility & ordering, no locking, no atomic compound operations. synchronized: mutual exclusion + acquire/release; good for complex invariants. JVM optimizations often reduce its cost. Atomic classes: CAS-based atomic compound operations; suited for lock-free updates and high-concurrency counters. Choose volatile for simple flags/publication, Atomic* for atomic updates, synchronized for multi-field invariants or critical sections. Java 9+ VarHandle VarHandle exposes finer-grained memory modes: getVolatile/setVolatile, getAcquire/setRelease, getOpaque/setOpaque, and CAS operations. Use VarHandle when you need precise, optimized memory-order semantics beyond plain volatile. JVM ↔ CPU mapping The JVM implements volatile semantics using platform-specific fences/intrinsics. Hardware models differ (x86 TSO is stronger than ARM/POWER), but the JMM abstracts those differences — program against the JMM, not CPU fences. Performance considerations & pitfalls volatile is typically cheaper than synchronized, but still incurs fences and can limit compiler/CPU optimizations. Avoid excessive volatile usage on hot paths without benchmarking (use JMH). Beware false sharing: frequently-updated volatile fields on the same cache line cause cache-line bouncing; consider padding or @Contended. Microbenchmarks can mislead; use JMH and realistic workloads. Tools for testing & debugging JCStress — concurrency testing for JMM behaviors. JMH — accurate microbenchmarking (measure volatile vs synchronized vs atomic). Static analyzers (FindBugs/SpotBugs) and careful stress tests for race detection. Best-practices checklist Use volatile for single-writer/multiple-reader flags, safe publication of immutable objects, and simple release-acquire patterns. Do NOT use volatile for compound read-modify-write operations or invariants spanning multiple fields. Prefer Atomic* or synchronized for atomic updates and multi-field invariants. When building low-level primitives, prefer VarHandle for explicit memory modes (acquire/release/opaque). Avoid publishing partially constructed objects; ensure construction completes before volatile publication. Measure performance (JMH) and mitigate false sharing when necessary. Further reading Java Language Specification — Java Memory Model chapter JSR-133 documentation (JMM changes for Java 5) Java Concurrency in Practice (Goetz et al.) VarHandle Javadoc and JCStress resources Closing summary volatile is a powerful, well-specified tool for visibility and ordering (release/acquire) problems. It is ideal for flags, safe publication, and some lock-free patterns, but it is not a replacement for locks or atomic classes for compound operations. Use the JMM as your guide, test with JCStress/JMH, and consider VarHandle for fine-grained memory control.

Let the lesson walk with you.

Podcast

Java Volatile podcast

0:00-3:28

Follow the trail that experts already trust.

Resources

Turn quick sparks into lasting recall.

Flashcards

Java Volatile flashcards

16 cards

Question

Click to flip
Answer

Prove the idea before it slips away.

Quizzes

Java Volatile quiz

13 questions

Which of the following best describes what the Java volatile keyword guarantees?

Read deeper, connect wider, own the subject.

Deep Article

Java volatile — a deep dive

TL;DR

  • volatile in Java is a lightweight concurrency mechanism that provides visibility and ordering guarantees but not mutual exclusion.
  • A write to a volatile variable happens-before every subsequent read of that same variable; this guarantees visibility of the write and ordering of other memory actions relative to those volatile accesses.
  • Use volatile for simple state flags, safe publication of immutable objects, some lazy initialization patterns (when used properly), and certain lock-free algorithms. Do not use it for compound actions (read-modify-write) — use synchronized, java.util.concurrent atomics, or CAS.
  • Since Java 5 (JSR-133) volatile semantics were strengthened; modern JVMs implement volatile via CPU memory fences appropriate for the platform. Java 9+ adds VarHandle and richer memory-ordering operations (acquire/release/opaque).

This article explains the history, theory (Java Memory Model), practical patterns, pitfalls, JVM/hardware mapping, modern alternatives (VarHandle, Atomic*), performance considerations, examples and recommended practice.


Contents

  • History and background
  • What volatile means (semantics)
  • Happens-before, synchronization order, and ordering guarantees
  • Atomicity vs visibility vs ordering
  • Practical uses and idioms
  • stop flag
  • safe publication of immutable objects
  • double-checked locking (corrected)
  • incorrect uses (counter example)
  • volatile vs synchronized vs Atomic*
  • Java 9+ VarHandle and fine-grained memory modes
  • Mapping to CPU memory models and JVM fences
  • Performance considerations and pitfalls (false sharing, microbenchmarks)
  • Tools, testing and debugging concurrency
  • Best-practices checklist
  • Further reading & references

History and background

Before Java 5 the Java Memory Model (JMM) was vague and allowed surprising behaviors when sharing mutable data between threads. In 2004 the JSR-133 effort revised and clarified the JMM for Java 5 and later, defining precise happens-before relationships and strengthening volatile semantics. After JSR-133:

  • Reads/writes of volatile variables are always atomic (for all scalar types).
  • A write to a volatile variable happens-before every subsequent read of that volatile.
  • Execution reordering around volatile accesses is constrained to provide visibility and ordering guarantees.

The change fixed classic concurrency bugs such as broken double-checked locking and incorrect safe publication.


What volatile means (semantics)

In Java, declaring a field volatile provides two primary guarantees:

  1. Visibility: A write to a volatile field by one thread is guaranteed to be visible to other threads that subsequently read that volatile variable.
  2. Ordering: Volatile reads/writes constrain reordering. Specifically:
  • A volatile write has "release semantics": memory writes before the volatile write in program order cannot be reordered to occur after the volatile write.
  • A volatile read has "acquire semantics": memory reads and writes after a volatile read cannot be reordered to occur before the volatile read.
  • A volatile write happens-before a subsequent volatile read of the same variable; the reading thread is guaranteed to see the effects of actions that happened-before the volatile write.

Important specifics:

  • Accesses (reads and writes) to a volatile variable are atomic for all primitive types (including long and double) and references.
  • volatile does NOT provide atomicity for compound operations (e.g., ++, x = x + 1, or check-then-act).
  • volatile does NOT provide mutual exclusion: two threads can concurrently write, causing races on higher-level invariants not protected by volatile semantics.

Note: Historically (pre-Java 5), long and double were not guaranteed to be atomic; JSR-133 fixed that.


Happens-before and ordering (formalized)

Key JMM concepts relevant for volatile:

  • Program Order: Within a single thread, actions appear in program order.
  • Monitor Synchronization Order: The ordering induced by synchronized blocks/monitors.
  • Volatile Order: All accesses to a particular volatile variable form a total order consistent with the program order.
  • Happens-before: A partial order. If action A happens-before B, then all memory writes by A are visible to B.

Crucial volatile rules:

  • A write to a volatile field happens-before every subsequent read of that field (in volatile order).
  • If A happens-before B and B happens-before C, then A happens-before C (transitivity).

Reordering constraints (intuitively):

  • Memory actions before a volatile write cannot be moved after it.
  • Memory actions after a volatile read cannot be moved before it.
  • This provides a lightweight “release-acquire” synchronization pair.

Example: Thread 1 does 1) set x = 42; 2) flag = true; // volatile write

Thread 2: 1) if (flag) // volatile read 2) read x -> guaranteed to see 42 (or later value). The volatile write -> read establishes happens-before and forces visibility.


Atomicity vs visibility vs ordering — what volatile gives and doesn't

  • Atomicity of single reads/writes: Yes (including long/double after Java 5).
  • Visibility of those writes to other threads: Yes (when reading the volatile or after happens-before).
  • Ordering with respect to other memory operations: Yes, only in the release/acquire sense described above.
  • Mutual exclusion: No.
  • Atomic compound operations: No.

Consequences: volatile is safe for single-writer-multiple-reader flags, status indicators, and safe publication of immutable objects; but it is not enough to make non-atomic compound updates (like increment) correct.


Practical uses and idioms

1) Stop flag (canonical example)

A typical example where volatile is enough:

```java class Worker implements Runnable { private volatile boolean running = true;

public void run() { while (running) { // do work } }

public void stop() { running = false; // volatile write } } ```

Because running is volatile, when another thread sets running = false, the worker thread will see the change promptly.

2) Safe publication of immutable objects

Volatile can be used to safely publish a reference to an immutable object:

```java class Holder { private static volatile MyImmutable obj;

static void init() { MyImmutable tmp = new MyImmutable(...); // fully constructed obj = tmp; // volatile write publishes the reference and prevents reordering }

static MyImmutable get() { return obj; // volatile read } } ```

Because the write to obj is volatile, any thread that reads the reference after it was set will see a fully initialized MyImmutable (assuming the object is immutable and properly constructed before assignment).

3) Double-checked locking (the corrected pattern)

Before JSR-133 double-checked locking was broken. With volatile it is correct:

```java class Singleton { private static volatile Singleton instance;

static Singleton getInstance() { Singleton result = instance; if (result == null) { synchronized (Singleton.class) { result = instance; if (result == null) { result = new Singleton(); instance = result; // volatile write } } } return result; } } ```

The volatile instance prevents reordering that could cause other threads to see a partially constructed object.

4) Sequence number / Stamped checks

Some lock-free algorithms use a volatile sequence or stamp to detect concurrent modification, e.g., optimistic read:

```java class VersionedContainer { private volatile long version; private Data data;

// writer: void update(Data newData) { version++; // make version odd (or use release semantics) data = newData; // write data version++; // make version even }

// optimistic reader: Data read() { while (true) { long v1 = version; // volatile read (acquire) Data d = data; // normal read long v2 = version; // volatile read if (v1 == v2 && (v1 & 1) == 0) return d; // no concurrent write } } } ```

This is similar to a sequence lock pattern — here volatile provides the necessary ordering for version reads/writes to detect modification. Implementation details matter; this is an advanced pattern.

5) Wrong pattern: volatile counter increment (NOT safe)

Volatile does not make compound updates atomic:

```java class Counter { private volatile int counter = 0;

void increment() { counter++; // Read-modify-write: still races, lost increments possible } } ```

counter++ is a read, increment, write sequence. Use AtomicInteger.incrementAndGet() or synchronized instead.


Volatile vs synchronized vs Atomic classes

  • Volatile
  • Good for: visibility, safe publication of immutable objects, simple flags, release-acquire ordering.
  • Cheap in many cases (no monitor, but JVM uses fences).
  • Not for: compound operations, mutual exclusion.
  • Synchronized (monitor)
  • Good for: mutual exclusion, complex invariants, compound operations.
  • Stronger guarantee: entering a monitor has an acquire memory effect and exiting has a release effect, and monitors establish mutual exclusion.
  • Slightly heavier but JVM optimizations (biased locking, lock elision, escape analysis) often reduce overhead.
  • Atomic classes (java.util.concurrent.atomic)
  • Good for: atomic compound operations via CAS (compare-and-set), often provides high performance non-blocking algorithms.
  • Provide methods with different memory semantics: get, set, lazySet, compareAndSet, getAndIncrement, etc.
  • Implemented with sun.misc.Unsafe intrinsics or VarHandles.

In practice:

  • Use volatile for simple visibility and release-acquire patterns.
  • Use Atomic* for lock-free atomic updates.
  • Use synchronized for complex ...

Ready to see the full tree?

Clone the preview to open the complete learning structure, practice tools, and generated study materials.