Fundamentals 17 min read

Java Happens‑Before Explained: Volatile, Synchronized & Instruction Reordering

This article explains Java’s happens‑before guarantees, covering instruction reordering, the visibility guarantees of volatile variables and synchronized blocks, and demonstrates how improper reordering can cause stale data or wasted resources through detailed code examples such as FrameExchanger and ValueExchanger.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
Java Happens‑Before Explained: Volatile, Synchronized & Instruction Reordering

Instruction Reordering

Modern CPUs can execute independent instructions in parallel. For example, the two statements below have no data dependency and can be executed concurrently:

a = b + c;

d = e + f;

Conversely, the following statements cannot be reordered because the second depends on the result of the first:

a = b + c;

d = a + e;

When these statements are part of a larger block, the CPU may reorder them to increase parallelism, as shown:

a = b + c;

d = a + e;

l = m + n;

y = x + z;

Reordering can improve performance by allowing more instructions to run simultaneously.

Reordering Problems in Multi‑CPU Computers

Reordering can cause subtle bugs in multithreaded programs. The example below illustrates a frame‑exchanging scenario where two threads cooperate to generate and render frames.

public class FrameExchanger {
    private long framesStoredCount = 0;
    private long framesTakenCount = 0;
    private boolean hasNewFrame = false;
    private Frame frame = null;

    // called by the producer thread
    public void storeFrame(Frame frame) {
        this.frame = frame;
        this.framesStoredCount++;
        this.hasNewFrame = true;
    }

    // called by the consumer thread
    public Frame takeFrame() {
        while (!hasNewFrame) {
            // busy‑wait until a new frame arrives
        }
        Frame newFrame = this.frame;
        this.framesTakenCount++;
        this.hasNewFrame = false;
        return newFrame;
    }
}

If the three statements in storeFrame are reordered so that hasNewFrame is set to true before frame is assigned, the consumer may read a stale frame, wasting CPU/GPU resources.

Java volatile Visibility Guarantee

The volatile keyword ensures that writes to a volatile variable are immediately flushed to main memory and reads always fetch the latest value from main memory, providing a visibility guarantee.

Volatile Write Visibility Guarantee

When a volatile variable is written, the write is performed directly to main memory, and all other variables visible to the writing thread are also flushed.

this.nonVolatileVarA = 34;
this.nonVolatileVarB = new String("Text");
this.volatileVarC = 300;

Because the volatile write occurs after the non‑volatile writes, the non‑volatile values are also synchronized to main memory.

Volatile Read Visibility Guarantee

When a volatile variable is read, the read is performed directly from main memory, and all other variables visible to the reading thread are also refreshed.

c = other.volatileVarC;
 b = other.nonVolatileB;
 a = other.nonVolatileA;

The read of volatileVarC forces a refresh of nonVolatileB and nonVolatileA from main memory.

Java Volatile Happens‑Before Guarantee

Declaring hasNewFrame as volatile in FrameExchanger adds ordering constraints:

public class FrameExchanger {
    private long framesStoredCount = 0;
    private long framesTakenCount = 0;
    private volatile boolean hasNewFrame = false;
    private Frame frame = null;

    public void storeFrame(Frame frame) {
        this.frame = frame;
        this.framesStoredCount++;
        this.hasNewFrame = true; // volatile write
    }

    public Frame takeFrame() {
        while (!hasNewFrame) {
            // busy‑wait
        }
        Frame newFrame = this.frame;
        this.framesTakenCount++;
        this.hasNewFrame = false;
        return newFrame;
    }
}

If the JVM reordered the statements so that hasNewFrame is set before frame, the consumer could exit the loop and read an old frame.

Volatile Write Happens‑Before

All instructions that occur before a volatile write are guaranteed to happen before that write.

public void storeFrame(Frame frame) {
    this.frame = frame;
    this.framesStoredCount++;
    this.hasNewFrame = true; // volatile write
}

The JVM may reorder the first two statements, but it cannot move them after the volatile write.

Volatile Read Happens‑Before

All instructions that occur after a volatile read are guaranteed to happen after that read.

int a = this.volatileVarA; // volatile read
int b = this.nonVolatileVarB;
int c = this.nonVolatileVarC;

The reads of nonVolatileVarB and nonVolatileVarC must remain after the volatile read.

Java Synchronized Visibility Guarantee

Synchronized blocks provide visibility guarantees similar to volatile.

Synchronized Entry Guarantee

When a thread enters a synchronized block, all variables visible to the thread are refreshed from main memory.

Synchronized Exit Guarantee

When a thread exits a synchronized block, all modified variables are flushed back to main memory.

Synchronized Visibility Example

public class ValueExchanger {
    private int valA;
    private int valB;
    private int valC;

    public void set(Values v) {
        this.valA = v.valA;
        this.valB = v.valB;
        synchronized(this) {
            this.valC = v.valC;
        }
    }

    public void get(Values v) {
        synchronized(this) {
            v.valC = this.valC;
        }
        v.valB = this.valB;
        v.valA = this.valA;
    }
}

Placing the synchronized block at the end of set ensures all updates are flushed after the method returns, while placing it at the beginning of get guarantees the reads see the latest values.

Java Synchronized Happens‑Before Guarantee

Synchronized blocks provide two happens‑before guarantees: one at the block’s start and one at its end.

Block‑Start Guarantee

All reads inside the block see values refreshed from main memory, so no read may be reordered before the block.

public void get(Values v) {
    synchronized(this) {
        v.valC = this.valC;
    }
    v.valB = this.valB;
    v.valA = this.valA;
}

Block‑End Guarantee

All writes performed before exiting the block are flushed to main memory, preventing writes from being reordered after the block.

public void set(Values v) {
    this.valA = v.valA;
    this.valB = v.valB;
    synchronized(this) {
        this.valC = v.valC;
    }
}

If the synchronized block were moved before the writes, the writes could occur after the block’s exit, breaking the visibility guarantee.

JavaconcurrencyInstruction Reordering
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

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.