Why Intuition Fails: Ordering, Instruction Reordering, and Volatile in Java Concurrency
The article explains how Java's memory model and compiler optimizations can reorder writes, causing ordering bugs in multithreaded programs, demonstrates the issue with simple and jcstress tests, and shows that declaring the flag as volatile restores the expected behavior.
Concurrency is notoriously difficult because programmers often rely on intuition that the execution order of statements matches the source order, which is not guaranteed on modern CPUs and JVMs.
A simple example creates two threads: thread T1 writes data = 666 and then isReady = true ; thread T2 spins on while (!isReady) {} and then reads r = data + 222 . Intuitively one expects r == 888 , but due to possible reordering the result 222 can appear.
boolean isReady = false;
int data = 0;
int r;
public void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
Thread t1 = new Thread(() -> {
data = 666;
isReady = true;
});
Thread t2 = new Thread(() -> {
while (!isReady) {};
r = data + 222;
});
t2.start();
t1.start();
t2.join();
if (r != 888) {
System.out.println(r);
}
}
}Running the program many times often shows no abnormal result, but deeper analysis reveals that the JVM, JIT compiler, or CPU may reorder the writes, making isReady become visible before data is updated.
To expose such subtle bugs, the article uses the OpenJDK jcstress framework, which runs the same scenario under a controlled stress test and records all possible outcomes.
@JCStressTest
@Outcome(id = "888", expect = Expect.ACCEPTABLE, desc = "expected")
@Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "expected")
@Outcome(expect = Expect.ACCEPTABLE_INTERESTING, desc = "abnormal")
@State
public class IsReadyTest {
int data = 0;
boolean isReady = false;
@Actor
void actor1() {
data = 666;
isReady = true;
}
@Actor
void actor2(I_Result r) {
if (!isReady) {
r.r1 = 0;
} else {
r.r1 = data + 222;
}
}
}The test reports both the expected result 888 and the unexpected result 222 , the latter indicating that isReady became true while data was still zero – a classic manifestation of instruction reordering.
Replacing the if check with a while loop (busy‑wait) reproduces the problem more dramatically, because the compiler may hoist the read of isReady out of the loop, turning the loop into an infinite spin.
@JCStressTest
@Outcome(id = "888", expect = Expect.ACCEPTABLE, desc = "expected")
@Outcome(expect = Expect.ACCEPTABLE_INTERESTING, desc = "abnormal")
@State
public class IsReadyTestError {
int data = 0;
boolean isReady = false;
@Actor
void actor1() {
data = 666;
isReady = true;
}
@Actor
void actor2(I_Result r) {
while (!isReady) {};
r.r1 = data + 222;
}
}The root cause is instruction reordering: the JVM/JIT is allowed to reorder independent writes for performance, and the CPU may also execute memory operations out of program order. Without proper synchronization, other threads can observe a later write before an earlier one.
The straightforward fix is to declare the flag isReady as volatile . A volatile write establishes a happens‑before relationship with subsequent volatile reads, preventing the write to data from being reordered after the write to isReady and ensuring visibility across threads.
int data = 0;
volatile boolean isReady = false;
// writer thread
data = 666;
isReady = true;
// reader thread
while (!isReady) {};
int r = data + 222; // r will always be 888In addition to ordering, volatile also disables certain CPU cache optimizations, guaranteeing that each read sees the most recent write.
In summary, writing correct concurrent Java code requires understanding of the Java Memory Model, the effects of instruction reordering, and the proper use of synchronization primitives such as volatile , locks, or higher‑level concurrent utilities.
JD Tech
Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.
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.