How Does Java’s volatile Keyword Ensure Thread Visibility and Prevent Reordering?

This article explains how Java's volatile keyword provides visibility guarantees and prevents instruction reordering through memory barriers, illustrates its behavior with code examples, discusses its limitations regarding atomicity, and outlines appropriate usage scenarios such as state flags and double‑checked locking.

Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
How Does Java’s volatile Keyword Ensure Thread Visibility and Prevent Reordering?

In Java, the volatile keyword is a lightweight synchronization mechanism that ensures the latest value of a variable is immediately visible to all threads.

Problems solved by volatile

Visibility guarantee : when a thread modifies a volatile variable, the new value is flushed to main memory and other threads read the fresh value from main memory instead of a cached copy.

Prevention of instruction reordering : the volatile keyword inserts memory barriers before and after read/write operations, ensuring that operations on the volatile variable are not reordered, preserving ordering.

Visibility example

Consider a program without volatile:

public class VolatileTest {
    private static boolean flag = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("Thread t1 start");
            while (!flag) {
                // busy wait
            }
            System.out.println("Thread t1 end");
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        flag = true;
        System.out.println("Main thread set flag to true");
    }
}

When the main thread changes flag, the worker thread does not see the change.

Adding volatile makes the change visible immediately: private static volatile boolean flag = false; Now the worker thread perceives the change as soon as it occurs.

Preventing reordering with double‑checked locking

The volatile keyword is essential in the double‑checked locking singleton pattern:

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Without volatile, the three steps of object creation may be reordered, causing another thread to obtain a partially constructed instance.

Allocate memory space

Assign the reference to the allocated memory (object not yet initialized)

Initialize the object

Reordering can lead to thread B seeing an uninitialized object.

Limitations of volatile

Although volatile guarantees visibility, it does not guarantee atomicity. Compound operations such as count++ are not atomic.

public class AtomicityExample {
    private volatile int count = 0;
    public void increment() {
        count++; // not atomic
    }
}

The increment consists of read‑modify‑write steps, which can interleave between threads, leading to lost updates.

Underlying mechanism

Visibility is provided by the cache‑coherence protocol (e.g., MESI). When a volatile write occurs, the cache line is marked Modified and other CPUs invalidate their copies, forcing a reload from main memory.

MESI states: Modified, Exclusive, Shared, Invalid.

Memory barriers

Volatile reads and writes are surrounded by memory barriers:

LoadLoad barrier – ensures prior loads complete before a volatile load.

StoreStore barrier – ensures prior stores complete before a volatile store.

LoadStore barrier – ensures a volatile load completes before subsequent stores.

StoreLoad barrier – ensures a volatile store completes before subsequent loads.

Write barrier (Store) guarantees previous writes become visible before the volatile write; read barrier (Load) guarantees subsequent reads see the latest value.

When to use volatile

Use volatile for variables that are written by a single thread and read by many, for state flags, for the double‑checked locking singleton, or simple timers. For atomicity, use synchronized or classes from java.util.concurrent.atomic.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavavolatileInstruction Reorderingmemory barrierthread visibility
Xuanwu Backend Tech Stack
Written by

Xuanwu Backend Tech Stack

Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.

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.