Fundamentals 11 min read

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.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Why a Java Volatile Example May Terminate Without 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 .

JavaJVMConcurrencyHotSpotvolatileMemory Model
Java Tech Enthusiast
Written by

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!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.