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...
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 themfence(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
- Use volatile for flags and simple publication patterns
- Use synchronized for compound operations
- Use Atomic* for lock-free counters
- final fields automatically get HB guarantees after the constructor
- Avoid manual HB management via Unsafe/VarHandle unless absolutely necessary
- 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
finalfields 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