Why a Java Volatile Example May Terminate Without Volatile
The program that loops on a non‑volatile boolean flag sometimes terminates because changing the counter to a volatile int or to an Integer causes the JVM to emit hidden memory barriers on reference writes, making the flag visible, but this behavior is JVM‑specific and not a reliable substitute for declaring the flag volatile.
Five years ago the author encountered a classic Java concurrency puzzle: a program with a non‑volatile boolean flag that seemed to loop forever, yet sometimes terminated.
The code is shown below:
public class VolatileExample {
private static boolean flag = false;
private static int i = 0;
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100);
flag = true;
System.out.println("flag 被修改成 true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
while (!flag) {
i++;
}
System.out.println("程序结束,i=" + i);
}
}Because flag is not declared volatile , the main thread may never see the update from the child thread, resulting in a dead loop. This is a well‑known interview question about the Java Memory Model (JMM) and the volatile keyword.
The author then performed two “micro‑adjustments”:
Marking the counter i as volatile . The program terminates, but the explanation is trivial – volatile forces visibility.
Changing i from the primitive int to the wrapper Integer . Surprisingly, the program also terminates.
Using DeepSeek, the author received an explanation that when i is an Integer , each i++ creates a new object and updates a static reference via the putstatic bytecode instruction. In HotSpot JVM, writing a reference may implicitly insert a memory barrier, which can flush other variables (including flag ) from the working memory to main memory, making the update visible.
The discussion then delves into the JMM rules (read from main memory, write back to main memory) and notes that ordinary variables have no mandatory synchronization. Some JVM implementations, however, add hidden synchronization on certain operations such as reference writes, which is not required by the JMM specification.
Further probing with DeepSeek confirmed that HotSpot indeed treats putstatic on object references as a potential memory barrier, causing the flag to become visible and the loop to exit. Adding volatile to i or flag explicitly introduces a memory barrier, guaranteeing the same effect.
In summary, the program’s termination without volatile on flag is a JVM‑specific side effect caused by reference writes on Integer (or any object) that may trigger memory barriers. The behavior is not portable and should not be relied upon; the correct solution remains to declare flag as volatile .
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
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.