Question 2 · Section 9

What is happens-before relationship?

Here: x = 10 happens-before ready = true (program order rule: in ONE thread, an earlier operation is visible to a later one), and ready = true happens-before the read of ready i...

Language versions: English Russian Ukrainian

Junior Level

Basic Understanding

Happens-before is Java’s guarantee of result visibility between threads.

Important: Happens-before does NOT mean that action A will execute earlier than B in time. Threads MAY execute in any order. HB only guarantees: if A happens-before B, then the result of A will be visible in B — as if A happened earlier.

Simple Example

// Thread 1:
int x = 10;             // Action A
ready = true;           // Action B (volatile)

// Thread 2:
if (ready) {            // Action C
    System.out.println(x); // Always prints 10!
}

Here: x = 10 happens-before ready = true (program order rule: in ONE thread, an earlier operation is visible to a later one), and ready = true happens-before the read of ready in another thread (volatile rule). By transitivity: x = 10 happens-before the reads in thread 2.

Why is this important?

Without happens-before, the compiler and processor can reorder instructions:

// WITHOUT happens-before guarantees:
int x = 10;
boolean flag = false; // without volatile

// Thread 1:           // Thread 2:
x = 10;               if (flag) {
flag = true;              System.out.println(x); // Might be 0!
// Processor may reorder              }

Middle Level

8 Happens-Before Rules (JSR-133)

The Java Memory Model defines 8 rules:

# Rule Description
1 Program Order In a single thread, each action HBs every action that appears later in the code
2 Monitor Lock Unlocking a monitor (exiting synchronized) HBs a subsequent lock of the same monitor
3 Volatile Variable A write to a volatile field HBs a subsequent read of the same field
4 Thread Start A call to Thread.start() HBs any action in the started thread
5 Thread Termination Any action in a thread HBs the termination of that thread (when join() returns)
6 Interruption A call to interrupt() HBs the detection of the interrupt (isInterrupted() or InterruptedException)
7 Finalizer The end of a constructor HBs the start of the finalize() method
8 Transitivity If A HBs B and B HBs C, then A HBs C

Examples for Each Rule

1. Program Order Rule

int a = 1;    // HB
int b = a + 1; // HB
int c = b + 1; // HB — order guaranteed within one thread

2. Monitor Lock Rule

synchronized(lock) {
    x = 42;  // Write inside synchronized
} // Release monitor

// In another thread:
synchronized(lock) {
    System.out.println(x); // Sees 42 — acquires the same monitor
}

3. Volatile Variable Rule

volatile boolean flag = false;
int data = 0;

// Thread 1:
data = 100;     // HB (program order)
flag = true;    // Volatile write

// Thread 2:
if (flag) {     // Volatile read — HB from write
    System.out.println(data); // Always 100!
}

4. Thread Start Rule

int sharedData = 50;
Thread t = new Thread(() -> {
    System.out.println(sharedData); // Always sees 50!
});
sharedData = 100;  // HB (program order) before start()
t.start();         // start() HBs all actions in thread t
// HB guarantees visibility ONLY of what happened-before start()
// sharedData = 100 (after start()) — NOT visible to thread t
// sharedData = 50 (before start()) — visible to thread t

5. Thread Termination Rule

Thread t = new Thread(() -> {
    result = compute(); // Action in thread
});
t.start();
t.join(); // join() returns — HB
System.out.println(result); // Sees the result!

Piggybacking

Thanks to transitivity, you can guarantee visibility of a group of regular variables through a single volatile:

int x = 0, y = 0, z = 0;
volatile boolean published = false;

// Writer thread:
x = 10;
y = 20;
z = 30;
published = true; // Volatile write — all previous writes "ride along"

// Reader thread:
if (published) { // Volatile read — "picks up" all previous writes
    System.out.println(x + y + z); // Always 60!
}

Senior Level

Under the Hood: Memory Fences

Each HB relationship is implemented through Memory Fences at the CPU level:

Memory Fence (memory barrier) — same as Memory Barrier. Prevents the processor from reordering operations around the barrier.

Barrier Type Prevents When inserted
LoadLoad Read cannot be moved upward past the barrier volatile read
StoreStore Write cannot be moved upward past the barrier volatile write
LoadStore Write cannot be moved upward past the barrier volatile read
StoreLoad Read cannot be moved upward past the barrier (most expensive) volatile write

On x86 architecture:

  • LoadLoad, LoadStore — free (x86 does not reorder reads)
  • StoreStore — free (x86 does not reorder writes)
  • StoreLoad — requires the mfence (Memory Fence) instruction on x86. The most expensive barrier, prevents ANY reordering around it.

CPU Reordering and Out-of-Order Execution

Processors execute instructions out of order for optimization:

// Source code:
A = 1;       // Instruction 1
B = 2;       // Instruction 2
flag = true; // Instruction 3 (volatile)

// Processor may execute:
flag = true; // First — cheaper
A = 1;       // Then
B = 2;       // Then

Without a volatile barrier, another thread may see flag = true, but A and B not yet written.

Performance and Highload

Cost of Memory Barriers

// Benchmark (approximate):
volatile write:  ~10-50 ns (depends on architecture)
regular write:   ~1 ns
synchronized:    ~50-500 ns (depends on contention)

More volatile variables = more “stalls” in the CPU pipeline.

False Sharing with volatile

public class VolatilePair {
    public volatile long v1; // In the same cache line (64 bytes)
    public volatile long v2; // Writing v1 invalidates v2!
}

Solution — @Contended:

public class ContendedPair {
    @Contended
    public volatile long v1; // In different cache lines

    @Contended
    public volatile long v2;
}

Run with -XX:-RestrictContended for Java 8+.

Diagnostics

JCStress (Java Concurrency Stress)

The only tool for empirical HB verification:

@JCStressTest
@Outcome(id = "0", expect = ACCEPTABLE, desc = "Not published")
@Outcome(id = "42", expect = ACCEPTABLE, desc = "Published correctly")
@Outcome(id = "0", expect = FORBIDDEN, desc = "Visibility bug!")
public class HBTest {
    int data = 0;
    volatile boolean ready = false;

    @Actor
    public void actor1() {
        data = 42;
        ready = true;
    }

    @Actor
    public void actor2(IntResult2 r) {
        if (ready) {
            r.r1 = data;
        }
    }
}

FindBugs/SpotBugs

Detects potential JMM violations:

// SpotBugs will find this:
public class Bug {
    private int data;        // Not volatile
    private boolean ready;   // Not volatile

    public void write() {
        data = 42;
        ready = true; // No HB! Another thread may not see data
    }
}

Best Practices

  1. Use volatile for flags and simple publication patterns
  2. Use synchronized for compound operations
  3. Use Atomic* for lock-free counters
  4. final fields automatically get HB guarantees after the constructor
  5. Avoid manual HB management via Unsafe/VarHandle unless absolutely necessary
  6. Test multithreaded code through JCStress, not just regular tests

Interview Cheat Sheet

Must know:

  • Happens-before is a visibility guarantee, not a chronological execution order
  • 8 JMM rules: Program Order, Monitor Lock, Volatile Variable, Thread Start, Thread Termination, Interruption, Finalizer, Transitivity
  • Thanks to transitivity, you can “piggyback” visibility of regular variables onto a single volatile
  • On x86, the StoreLoad barrier requires mfence — the most expensive of all barriers
  • Without happens-before, the processor and JIT can reorder instructions
  • final fields automatically receive HB guarantees after the constructor completes
  • HB guarantees result visibility: if A HBs B, the result of A will be visible in B

Frequent follow-up questions:

  • Does happens-before mean A executes before B in time? — No, only that the result of A will be visible in B
  • How to guarantee visibility of multiple regular variables? — Write them, then write a volatile flag (piggybacking via transitivity)
  • Which HB rule covers synchronized? — Monitor Lock: unlocking a monitor HBs a subsequent lock of the same monitor
  • How does HB differ from memory barrier? — HB is a guarantee at the JMM level, memory barrier is the implementation at the CPU level

Red flags (do NOT say):

  • “Happens-before means operations execute in order” — no, it’s about visibility
  • “Just use volatile for all variables” — no, one volatile “carries” all previous writes
  • “Thread.start() has nothing to do with HB” — it does: start() HBs all actions in the started thread
  • “On x86 all barriers are expensive” — LoadLoad and StoreStore on x86 are free

Related topics:

  • [[1. What is the difference between synchronized and volatile]] — volatile rule as one of the HB rules
  • [[3. What is visibility problem]] — HB solves the visibility problem at the JMM level
  • [[4. What is monitor in Java]] — Monitor Lock rule is related to synchronized
  • [[5. How does synchronized work at monitor level]] — memory barriers on entry/exit