Why Volatile Solves Memory Visibility and Reordering in Java?
This article explains how the volatile keyword guarantees visibility of shared variables and prevents instruction reordering in Java, covering the Java Memory Model, memory barriers, practical code examples, and the Happens‑Before principle for reliable multithreaded programming.
Memory Visibility
Two versions of a simple program illustrate the visibility problem. In the first version a static boolean isRunning is modified by a child thread without the volatile keyword. The main thread loops while isRunning is true and never observes the change, so the program never reaches the final System.out.println statement.
public class TestVolatile {
private static boolean isRunning = true;
public static void main(String[] args) {
int i = 0;
Thread t = new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
isRunning = false; // non‑volatile write
});
t.start();
while (isRunning) { i++; } // may loop forever
System.out.println("master thread is currently here");
}
}Adding the volatile modifier forces the write to be flushed to main memory and makes the change visible to the main thread, allowing the loop to terminate.
public class TestVolatile {
private static volatile boolean isRunning = true; // volatile
// main method unchanged
}Java Memory Model (JMM)
The JMM defines two memory areas:
Main memory – a shared heap that all threads can read from and write to.
Working memory – a private cache for each thread (registers, L1/L2 caches) that holds a copy of variables.
A write to a volatile variable forces the updated value to be written back from the thread’s working memory to main memory. A subsequent volatile read fetches the value directly from main memory, guaranteeing visibility across threads.
Memory Barriers
To enforce these guarantees the JVM inserts hardware memory‑barrier instructions around volatile accesses. Four barrier types are used:
StoreStore
LoadLoad
StoreLoad
LoadStore
For a volatile write the JVM emits a StoreStore barrier before the write and a StoreLoad barrier after it. For a volatile read it emits a LoadLoad barrier before the read and a LoadStore barrier after it. These barriers prevent the CPU or compiler from reordering the volatile operation with surrounding memory accesses.
Instruction Reordering Example
The following code demonstrates a classic reordering scenario. Two threads write to a and b and then read the other variable. Without any ordering guarantees the main thread may observe x == 0 && y == 0, a state that cannot occur under sequential execution.
public class TestVolatile1 {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
count++;
x = 0; y = 0; a = 0; b = 0;
Thread t1 = new Thread(() -> { a = 1; x = b; });
Thread t2 = new Thread(() -> { b = 1; y = a; });
t1.start(); t2.start();
t1.join(); t2.join();
if (x == 0 && y == 0) {
System.out.println("Attempt " + count + ": reordering observed, x=" + x + ", y=" + y);
break;
}
}
}
}Marking a and b as volatile eliminates the reordering because each write is now a volatile write and each read a volatile read, which are ordered by the memory barriers.
private static volatile int a = 0, b = 0;Happens‑Before Principle
The core guarantee of the JMM is the happens‑before relation. Two rules that are directly relevant to volatile are:
Program order principle : Within a single thread, actions occur in the order dictated by the control flow.
Volatile variable principle : A write to a volatile variable happens‑before every subsequent read of that same variable.
When both rules hold, the effects of one thread become visible to another and instruction reordering that would break correctness is prohibited.
Summary of Guarantees
Volatile writes insert StoreStore and StoreLoad barriers, forcing the new value to main memory.
Volatile reads insert LoadLoad and LoadStore barriers, guaranteeing that the latest value is observed.
The combination of these memory barriers with the happens‑before relation resolves both visibility and reordering problems in multithreaded Java code.
Senior Tony
Former senior tech manager at Meituan, ex‑tech director at New Oriental, with experience at JD.com and Qunar; specializes in Java interview coaching and regularly shares hardcore technical content. Runs a video channel of the same name.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
