Why synchronized Doesn't Prevent Instruction Reordering (and Why DCL Needs volatile)

The article explains that Java's synchronized keyword guarantees mutual exclusion but cannot stop the CPU or compiler from reordering instructions inside the critical section, leading to a broken double‑checked locking pattern unless the instance field is declared volatile.

Programmer XiaoFu
Programmer XiaoFu
Programmer XiaoFu
Why synchronized Doesn't Prevent Instruction Reordering (and Why DCL Needs volatile)

synchronized actually protects what

Many developers assume that synchronized prevents instruction reordering because it promises ordered execution, but this guarantee only holds under a larger precondition.

Analogy: a locked bathroom

Think of synchronized as a single‑person bathroom with a lock. Thread A locks the door, so other threads must wait outside, giving the appearance of atomicity and ordering from the outside. Inside the bathroom, however, the CPU and compiler may rearrange the steps (e.g., taking off pants before flushing) for efficiency, which synchronized cannot control.

Double‑checked locking (DCL) example

public class Singleton {
    // Warning: omitting volatile can cause disaster
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) { // first check, no lock
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); // critical line
                }
            }
        }
        return instance;
    }
}

Object creation consists of three steps:

Allocate memory.

Run the constructor to initialize the object.

Assign the reference to instance.

Normally the order is 1 → 2 → 3, but without a memory barrier the CPU may reorder it to 1 → 3 → 2.

Disaster scenario when reordering occurs

Thread A enters the synchronized block and starts new Singleton().

Thread A executes step 1, then step 3, so instance becomes non‑null while the constructor has not run.

Thread B reaches the outer if (instance == null) without acquiring the lock, sees instance != null, and returns the half‑constructed object.

Thread B uses the object, encountering NullPointerException or corrupted state, causing the system to crash.

This shows that synchronized can lock Thread A inside the block, but it cannot stop Thread B from observing the partially constructed object because the first check is outside the lock.

Why volatile fixes the problem

private static volatile Singleton instance;

Beyond guaranteeing visibility, volatile inserts a memory barrier that forbids the reordering of steps 1, 2, and 3. The CPU must complete allocation and construction before publishing the reference, so any thread that sees instance != null receives a fully initialized object.

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.

JavaConcurrencyvolatilesynchronizedinstruction reorderingdouble-checked locking
Programmer XiaoFu
Written by

Programmer XiaoFu

xiaofucode.com – a programmer learning guide driven by the pursuit of profit

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.