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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer XiaoFu
xiaofucode.com – a programmer learning guide driven by the pursuit of profit
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
