Fundamentals 7 min read

Instruction Reordering and Ordering in Java Double-Checked Locking

The article explains the difference between instruction reordering and ordering, analyzes how the double‑checked locking pattern can suffer from reordering at the bytecode level, and demonstrates how synchronized provides ordering while volatile only prevents visibility issues, using Java code examples and detailed bytecode analysis.

Architecture Digest
Architecture Digest
Architecture Digest
Instruction Reordering and Ordering in Java Double-Checked Locking

I) Instruction Reordering

First, it is essential to distinguish that instruction reordering and ordering are not the same; this distinction is crucial.

Common statements:

volatile guarantees memory visibility and prevents instruction reordering but does not guarantee atomicity.

synchronized guarantees atomicity, visibility, and ordering.

Note: the ordering mentioned here does not mean that instruction reordering is prohibited.

Example: In the double‑checked singleton pattern, if synchronized could prevent reordering, volatile would be unnecessary.

2) DCL Bytecode Analysis of Instruction Reordering

Understanding that the statement Object obj = new Object(); is not atomic; it consists of three steps:

Allocate memory and create the object.

Invoke the object's constructor.

Assign the reference to obj .

a) DCL double‑checked locking code

public class MySingleton {

    private static MySingleton INSTANCE;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (MySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new MySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

b) Corresponding bytecode (excerpt)

From the bytecode we see that the statement new MySingleton(); corresponds to lines 17, 20, 21, and 24 (line 20 is a reference copy and can be ignored).

Line 17: Allocate memory for a MySingleton object.

Line 21: Invoke the constructor.

Line 24: Assign the reference to the static variable INSTANCE .

The expected execution order is 17 → 21 → 24, but the JVM may reorder it to 17 → 24 → 21 for performance.

Problems caused by instruction reordering

Thread T1 may execute 17, 24, 21, assigning the reference before the constructor finishes.

Thread T2 reads if (INSTANCE == null) , finds it non‑null, and returns the partially constructed object, leading to unpredictable behavior.

Thus, instruction reordering is harmless in a single‑threaded context but can produce subtle bugs in multithreaded scenarios.

II) Ordering

Since synchronized cannot prevent instruction reordering, what ordering does it guarantee?

It ensures that when multiple threads invoke methods protected by synchronized , the concurrent execution becomes serial: the thread that acquires the lock executes first.

1) Code Example

Two threads T1 and T2 both obtain the singleton instance and call a synchronized test method.

public class MySingleton {

    private static MySingleton INSTANCE;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (MySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new MySingleton();
                }
            }
        }
        return INSTANCE;
    }

    public static void test(final MySingleton singleton) {
        synchronized (MySingleton.class) {
            System.out.println(singleton);
        }
    }
}

Test code:

public class MySingletonTest {
    // Both threads obtain the singleton and invoke the synchronized test method
    public static void main(final String[] args) {
        new Thread(() -> {
            MySingleton instance = MySingleton.getInstance();
            MySingleton.test(instance);
        }, "t1").start();
        new Thread(() -> {
            MySingleton instance = MySingleton.getInstance();
            MySingleton.test(instance);
        }, "t2").start();
    }
}

Even if T2 obtains the reference before the constructor finishes, calling MySingleton.test(instance) does not cause issues because the synchronized block serializes the execution, ensuring the object is fully constructed before the method body runs.

Therefore, synchronized guarantees ordering by converting concurrent execution into serial execution, but it does not eliminate internal instruction reordering.

JavaConcurrencyvolatilesynchronizedinstruction reorderingDouble-Checked Locking
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.