Happens-Before
Happens-before is the precise relationship the Java Memory Model uses to say when one thread's action is guaranteed visible to another — the rule beneath every synchronization primitive.
Happens-before is the formal relationship the Java Memory Model (JLS Chapter 17) defines to specify when the effects of one action are guaranteed to be visible to, and ordered before, another action — even across different threads. It is not about wall-clock time ('this really happened earlier'); it is a specification-level ordering guarantee. If action X happens-before action Y, then X's effects (writes to memory) are guaranteed visible to Y, and X is guaranteed to appear to execute before Y from the perspective of any thread observing both.
What Happens-Before Actually Means
'Happens-before' is a partial ordering the JLS defines over the actions of a program's execution. It does not mean 'happens earlier in real time' — two actions with no happens-before relationship between them can be reordered, and neither is guaranteed visible to the other in any particular order, even if one genuinely executed first on the clock. The relationship must be established by specific rules the JLS enumerates; it is never assumed by default between unrelated statements in different threads.
If there is no happens-before relationship between a write in one thread and a read of the same variable in another thread, the JMM makes no visibility or ordering guarantee at all — this is precisely the definition of a data race.
Program Order Rule
Within a single thread, each action happens-before every subsequent action in that thread's program order. This is the only happens-before rule that requires no synchronization — it simply reflects that a single thread always observes its own actions in the order it performed them.
int x = 1; // action 1
int y = x + 1; // action 2 - happens-after action 1, within this thread
// Program order guarantees action 1 happens-before action 2,
// but says NOTHING about visibility to other threads.Monitor Lock / Unlock Rule
An unlock on a monitor (leaving a synchronized block/method) happens-before every subsequent lock on that same monitor (entering a synchronized block/method on the same object) by any thread. This is the classical basis of mutual exclusion providing visibility, not just exclusion: whatever a thread wrote before releasing a lock is guaranteed visible to the next thread that acquires that same lock.
Because both methods synchronize on the same lock object, write()'s unlock happens-before read()'s lock, guaranteeing read() sees the written value.
class Box {
private int value;
private final Object lock = new Object();
void write(int v) {
synchronized (lock) { value = v; } // unlock here happens-before...
}
int read() {
synchronized (lock) { return value; } // ...the lock acquired here
}
}Volatile Write / Read Rule
A write to a volatile field happens-before every subsequent read of that same field by any thread that actually observes the written value. This gives volatile fields a lock-free way to establish a happens-before relationship for simple flag/publication patterns, without the mutual-exclusion cost of a lock.
class Publisher {
private int payload;
private volatile boolean ready;
void publish(int value) {
payload = value; // ordinary write
ready = true; // volatile write - happens-before any read seeing ready==true
}
int consume() {
if (ready) { // volatile read
return payload; // guaranteed to see the published value
}
return -1;
}
}Thread Start Rule
A call to Thread.start() happens-before any action in the newly started thread. This means everything the starting thread did before calling start() is guaranteed visible inside the new thread's run() method — you don't need extra synchronization just to hand initial state to a thread you are about to start.
int[] data = {1, 2, 3}; // set up before starting the thread
Thread t = new Thread(() -> {
System.out.println(data[0]); // guaranteed to see 1, thanks to the start rule
});
t.start();Thread Termination / Join Rule
All actions in a thread happen-before another thread successfully returns from a join() call on that thread. This lets a thread that waits for another thread to finish (via join) safely observe everything that thread did, without additional synchronization.
int[] result = new int[1];
Thread worker = new Thread(() -> result[0] = computeExpensiveValue());
worker.start();
worker.join(); // blocks until worker finishes
System.out.println(result[0]); // guaranteed to see the computed valueFinal Field Initialization Semantics
The JMM gives special guarantees to final fields: if an object is properly constructed (its constructor does not leak a reference to this before finishing), then any thread that obtains a reference to that object after construction is guaranteed to see the correctly initialized values of its final fields, without needing additional synchronization. This is the mechanism that makes immutable objects safely publishable across threads even without a lock, as long as construction does not leak this.
This guarantee applies specifically to final fields; it does not extend to non-final fields set in the constructor, and it is voided if the constructor lets 'this' escape (e.g. by registering a listener or starting a thread that uses the object before the constructor completes).
Transitivity
Happens-before is transitive: if action A happens-before action B, and B happens-before action C, then A happens-before C — even if A and C are in different threads with no direct rule linking them. This is what lets a chain of thread hand-offs (e.g. thread 1 finishes, thread 2 starts and finishes, thread 3 joins thread 2) safely propagate visibility across the whole chain.
Thread 1: write X -----> happens-before (thread start rule) ----->
Thread 2: read X, write Y -----> happens-before (join rule) ----->
Thread 3: read Y
By transitivity, Thread 1's write to X is visible-consistent all the way through to Thread 3, even though there is no direct rule between Thread 1 and Thread 3.java.util.concurrent Happens-Before Guarantees
Beyond the core JLS rules, the java.util.concurrent package documents its own happens-before guarantees so higher-level utilities can be relied on directly: placing an item into a BlockingQueue happens-before another thread removing it; a CountDownLatch reaching zero via countDown() happens-before a successful return from await(); submitting a task to an ExecutorService happens-before that task begins execution; actions before unlock() on a Lock happen-before actions after a subsequent lock() on the same lock.
What Happens-Before Does NOT Mean
Happens-before is not a claim about real-world timing — two actions can happen-before each other in the JMM sense while, on real hardware, executing in an order that a human observer watching a clock might describe differently, as long as no thread can observe a violation of the guarantee. It also does not imply mutual exclusion by itself (a happens-before relationship can exist without any lock, e.g. via volatile) and does not, on its own, make compound operations atomic.
Key Takeaways
Happens-before is a specification-level ordering/visibility guarantee, not a claim about real clock time.
Program order gives happens-before within a single thread automatically; nothing is guaranteed across threads without an explicit rule.
Monitor unlock/lock and volatile write/read are the two core cross-thread happens-before rules for ordinary shared-state coordination.
Thread.start() happens-before the new thread's first action; a thread's actions happen-before a successful join() on it.
Final field initialization is safely visible to any thread obtaining a reference after construction, provided the constructor does not let 'this' escape.
Happens-before is transitive, letting visibility propagate across chains of otherwise-unrelated threads.
Without an applicable happens-before rule linking a write and a read of shared state, that access is a data race with no visibility guarantee.
// Assuming two unrelated threads' actions are ordered just because thread A's code runs 'first' in wall-clock time
// Only rely on an explicit happens-before rule (lock/unlock, volatile write/read, start/join, etc.) to guarantee cross-thread ordering
Wall-clock timing gives no JMM guarantee; without an applicable happens-before rule, the visibility and ordering between two threads' actions is simply undefined.
class Config { private int value; // not final, set in constructor Config(int v) { value = v; } } // published to another thread without synchronization, relying on constructor ordering class Config { private final int value; // final gives safe-publication guarantees Config(int v) { value = v; } }
Only final fields get the JMM's safe-initialization guarantee for objects published without explicit synchronization; a non-final field set in the constructor has no such guarantee.
class Leaky { Leaky() { registry.register(this); // 'this' escapes before construction finishes this.value = 42; } } class Safe { Safe() { this.value = 42; } } // register only after construction completes: Safe s = new Safe(); registry.register(s);
Letting 'this' escape during construction (e.g. registering it with another object/thread) voids the final-field safe-publication guarantee, because another thread could observe the object before its constructor finishes.
Does happens-before mean one action literally executed before another in real time?
No. Happens-before is a specification-level guarantee about visibility and apparent ordering, not a claim about wall-clock execution order. Actions can be reordered by the compiler/CPU as long as no thread observes a violation of the happens-before guarantees that do apply.
What is the only happens-before rule that requires no explicit synchronization?
The program order rule: within a single thread, each action happens-before every subsequent action performed by that same thread.
How does the monitor lock/unlock rule provide more than just mutual exclusion?
It also establishes happens-before: an unlock on a monitor happens-before every subsequent lock on the same monitor by any thread, guaranteeing that whatever the unlocking thread wrote is visible to the thread that next acquires the lock — not just that they cannot run inside the block simultaneously.
What happens-before guarantee does Thread.start() provide?
Every action the starting thread performed before calling start() happens-before any action performed inside the newly started thread's run() method, so initial state set up before start() is safely visible inside the new thread.
Why is the final-field safe-publication guarantee voided if a constructor lets 'this' escape?
The guarantee relies on no other thread being able to observe the object until construction completes. If the constructor passes 'this' to another thread or registers it somewhere reachable before finishing, another thread could see the object in a partially-constructed state, bypassing the guarantee entirely.
What does transitivity of happens-before allow you to conclude in a chain like thread1 -> thread2 -> thread3?
If action A in thread1 happens-before action B in thread2 (e.g. via thread1 starting thread2), and B happens-before action C in thread3 (e.g. via thread2 joined by thread3), then by transitivity A happens-before C, even though there is no direct rule linking thread1 and thread3.
If there is no happens-before relationship between a write in one thread and a read of the same variable in another, what does the JMM guarantee about what the reader sees?
Nothing — this is precisely the definition of a data race, and the JMM provides no guarantee about visibility or ordering for that access.
If Thread B observes flag == true, what is it guaranteed to print for x?
5. Because holder.flag is volatile, the write flag = true happens-after x = 5 in Thread A's program order, and by the volatile write/read rule, that write to flag happens-before Thread B's read of flag that observes true. By transitivity, x = 5 also happens-before Thread B's read of x, so 5 is guaranteed to be visible.
Write a program that safely publishes an immutable object across threads using final fields, and explain which happens-before rule makes it safe.
public class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; // constructor does NOT let 'this' escape } public int getX() { return x; } public int getY() { return y; } public static void main(String[] args) throws InterruptedException { ImmutablePoint[] holder = new ImmutablePoint[1]; Thread writer = new Thread(() -> holder[0] = new ImmutablePoint(3, 4)); writer.start(); writer.join(); System.out.println(holder[0].getX() + ", " + holder[0].getY()); } }
Because x and y are final and the constructor does not let 'this' escape, the JMM's final-field initialization guarantee ensures any thread that later obtains a reference to the ImmutablePoint sees correctly initialized x and y — combined here with the thread join rule (writer's actions, including full construction, happen-before the main thread's successful join() return), the printed values are guaranteed to be 3, 4.
Why does using synchronized on both increment() and get() guarantee that get() always sees the latest count, even without volatile?
Both methods synchronize on the same implicit monitor (the Counter instance itself). By the monitor lock/unlock happens-before rule, the unlock at the end of any increment() call happens-before the lock acquired by any subsequent call to get() (or another increment()), guaranteeing visibility of the latest written value — volatile is unnecessary here because the lock itself establishes the happens-before relationship.
class Holder {
int x;
volatile boolean flag;
}
// Thread A:
holder.x = 5;
holder.flag = true;
// Thread B:
if (holder.flag) {
System.out.println(holder.x);
}class Counter {
private int count = 0;
public synchronized void increment() { count++; }
public synchronized int get() { return count; }
}