happens-before is the JMM's ordering relation: if action A happens-before action B, then the memory effects of A are guaranteed to be visible to and ordered before B. It is the single guarantee the JMM gives you — anything not ordered by happens-before may be reordered or appear stale.
What the relation means
happens-before is not "happens earlier in wall-clock time." It is a partial order over actions that, where it holds, forces both visibility (B sees A's writes) and ordering (A is not reordered after B). If two conflicting accesses (at least one a write) are not ordered by happens-before, you have a data race and the result is undefined.
The main rules
Thread A Thread B
-------- --------
x = 1; \
\ (program order within A)
vol = true; -------\----------> if (vol) { (volatile write -> volatile read)
read x; // sees 1, transitively
}- Program order: each action in a thread happens-before every later action in that same thread.
- Monitor lock: an unlock of a monitor happens-before every subsequent lock of that same monitor.
- Volatile: a write to a volatile field happens-before every subsequent read of that same field.
- Thread start:
Thread.start()happens-before any action in the started thread. - Thread termination: every action in a thread happens-before another thread returning from
join()on it (or seeingisAlive() == false). - Interruption: a call to
interrupt()happens-before the interrupted thread detecting it. - Constructor / finalizer: the end of a constructor happens-before the start of the object's
finalize(). - Transitivity: if A hb B and B hb C, then A hb C.
int data;
volatile boolean ready;
void writer() {
data = 42; // (1) program order
ready = true; // (2) volatile write
}
void reader() {
if (ready) { // (3) volatile read
use(data); // (4) sees 42: (1) hb (2) hb (3) hb (4)
}
}
Transitivity is the workhorse: the volatile edge (2)→(3) drags the plain write (1) along, so data is published without being volatile itself.