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.

Senior Tony
Senior Tony
Senior Tony
Why Volatile Solves Memory Visibility and Reordering in Java?

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.

Memory visibility diagram
Memory visibility diagram
Happens‑Before diagram
Happens‑Before diagram
Javavolatilememory modelJMMInstruction Reordering
Senior Tony
Written by

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.

0 followers
Reader feedback

How this landed with the community

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.