skip to content

Happens-Before Relationship

The partial inter-thread ordering that guarantees one action's effects are visible to another, and the rules that create an edge: program order, lock release before acquire, a write to a synchronization variable before a read of it, thread start and join, and transitivity. Interviewers push on the point that happens-before is a visibility-and-ordering guarantee, not a statement about wall-clock time.

on this pageshow

questions

3

Which specific rules does the Java Language Specification list as creating a happens-before edge between two actions? Enumerate them.

level: middleimportance: must knowfreq 65%

answer

  1. closed rule set + transitivity
  2. program order; unlock→lock; volatile write→read
  3. start()→child; child→join()
  4. interrupt→detection; ctor end→finalizer
  5. "subsequent" = synchronization order

basics

~20 s

Program order within a thread; unlocking a monitor before any later lock of it; a volatile write before any later read of that field; Thread.start before the new thread's actions; a thread's actions before a successful join; interrupt before the interrupt is detected; constructor end before the finalizer; default writes before any thread starts; plus transitivity.

solid answer

~60 s

The specification enumerates a small closed set, and every other ordering in Java is built from it: - **Program order** — within one thread, each action happens-before every action later in program order. - **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 field. - **Thread start** — a call to `Thread.start()` happens-before every action in the started thread. - **Thread termination / join** — all actions in a thread happen-before another thread returns successfully from `join()` on it, or sees `isAlive()` return false. - **Interruption** — a thread interrupting another happens-before the interrupted thread detects it, via `InterruptedException` or an interrupt-status check. - **Finalization** — the end of an object's constructor happens-before the start of its finalizer. - **Default initialization** — the default write of every field happens-before the first action of any thread. - **Transitivity** — if A happens-before B and B happens-before C, then A happens-before C. "Subsequent" means later in the synchronization order, not later in wall-clock time.

code

java · 15 lines
java
class Publisher {
    private int data;                // plain
    private volatile boolean ready;  // the edge carrier

    void producer() {
        data = 42;               // 1
        ready = true;            // 2  volatile write
    }

    void consumer() {
        if (ready) {             // 3  volatile read; if it sees true, 2 hb 3
            use(data);           // 4  guaranteed to see 42: 1 hb 2 hb 3 hb 4
        }
    }
}

go deeper

for a junior

Recall the definition and the two everyday rules — program order, and the lock and volatile edges — and know that the relation is what makes one thread's writes visible to another.

for a middle

Enumerate the full rule list including thread start, join, interrupt and default initialization, and show a worked chain such as plain write → volatile write → volatile read → plain read.

for a senior

Emphasize transitivity as the tool for reasoning about real code, the same-monitor and same-field requirements, and that library guarantees are derived from these rules rather than added to them.

for a principal

Present happens-before as the contract boundary: the specification fixes a minimal closed rule set so implementations retain freedom to reorder, and API designs should document which edges they establish so callers can compose them.

## What an edge means Happens-before is a partial order over actions in a program. If action A happens-before action B, then everything A did to memory is visible to B, and B is ordered after A. If neither A happens-before B nor B happens-before A, and at least one is a write to the same location, the program contains a **data race** and the reader may observe either value. The specification does not describe this by listing forbidden optimizations. It gives a closed set of rules that *create* the relation, then closes the set under transitivity. Everything else in Java concurrency — every guarantee in the concurrency libraries — is stated in terms of these edges. ## The rules, with what each is actually for **Program order.** Within a single thread, each action happens-before every action that follows it in program order. This is why single-threaded code always behaves as written: the thread cannot observe its own reorderings. It notably does *not* mean the JIT and CPU refrain from reordering; it means any reordering must be undetectable to that thread. **Monitor lock.** An unlock on monitor *m* happens-before every subsequent lock on *m*. This is the edge behind `synchronized`. Note both halves are needed: two threads that lock *different* monitors get no edge, and a thread that reads a field without locking at all gets no edge no matter how carefully the writer locks. **Volatile.** A write to a volatile field happens-before every subsequent read of *that same field*. This is the only edge that does not involve blocking, and it is the basis for lock-free publication. Reads and writes of `java.util.concurrent.atomic` classes provide the same ordering as volatile reads and writes. **Thread start.** `t.start()` happens-before any action in thread *t*. Therefore everything the parent thread did before starting the child is visible to the child — the single most commonly relied-upon edge in ordinary code, usually without anyone noticing. **Thread termination and join.** All actions in thread *t* happen-before any action in another thread that detects *t* has terminated — by returning from `t.join()` or by observing `t.isAlive()` as false. This makes the classic fork-then-join pattern correct with no other synchronization. **Interruption.** If thread *a* interrupts thread *b*, `a`'s call to `interrupt()` happens-before *b* detecting the interrupt — either by throwing `InterruptedException` or by observing a true result from `isInterrupted()`/`interrupted()`. This is what makes cancellation data set before the interrupt visible to the cancelled thread. **Finalization.** The end of an object's constructor happens-before the start of its finalizer. Without this rule, a finalizer running on another thread could observe a half-built object. **Default initialization.** The write of the default value (`0`, `false`, `null`) to every field happens-before the first action of every thread. This is why an uninitialized field reads as its default and never as garbage — there is a defined write behind it, not merely unallocated memory. **Transitivity and composition.** The relation chains. If thread A writes a field then releases a lock, thread B acquires that lock and writes a volatile flag, and thread C reads the flag, then A's write is visible to C — through two different kinds of edge, with no direct relationship between A and C. Transitivity is what makes the small rule set expressive enough to describe real programs. ## The critical caveat on "subsequent" Every rule says "subsequent", which is defined over the **synchronization order** — a total order the model imposes over synchronization actions — not over a clock. It gives a well-defined notion of later for those actions specifically, which is why the volatile rule can be stated at all. The point matters for reasoning: an edge is a guarantee about *ordering and visibility*, never a promise that one action's instructions execute before another's. Two actions with no edge between them may still occur in a definite temporal order every single run, and the model still permits the reader to see the stale value. ## How this is used in practice When asked "is this code correct?", the mechanical procedure is: identify the write and the read that must be ordered, then find a chain of the rules above connecting them. If no chain exists, the code has a race, however unlikely it looks.

  • Where do guarantees such as "submitting a task to an executor orders everything before the submission with the task's execution" come from — are they extra rules?
    No, they are consequences. The concurrency library documents such orderings for its own types — executors, `BlockingQueue`, `CountDownLatch`, `Future`, locks — but each is implemented internally with the same primitives (volatile writes, monitor or lock release/acquire) and derived through transitivity. The rule set in the specification stays closed; the library merely publishes the edges its implementations establish.
  • Two threads synchronize on different lock objects around the same field. Is there an edge?
    No. The monitor rule requires an unlock and a subsequent lock of the *same* monitor. Locking distinct monitors gives mutual exclusion against nobody and creates no ordering, so the field access is a data race despite both threads using `synchronized`.
  • Does program order mean the JIT may not reorder statements inside a method?
    It may reorder freely, as long as the executing thread cannot tell. Program order guarantees the *thread's own* view is consistent with source order; it says nothing about what another thread observes, which is why cross-thread correctness needs one of the other edges.

saying these in an interview costs you the question

  • Reciting only volatile and synchronized and missing the start/join/interrupt edges.
  • Believing happens-before means one action executes earlier in real time.
  • Thinking synchronizing on any lock creates an edge, regardless of which monitor.
  • Assuming a volatile read of field X orders a write to a different volatile field Y with no chain.
  • Claiming happens-before requires both threads to run on the same core or be adjacent in time.

context

open as a page

A thread writes several plain (non-volatile) fields, then starts a worker thread that reads them; later the parent calls `join()` and reads fields the worker wrote. Which of these reads are guaranteed correct, and by which rule?

level: middleimportance: should knowfreq 45%

basics

~20 s

Both are guaranteed. Thread.start() happens-before every action in the started thread, so the worker sees everything written before the start. All actions in a thread happen-before a successful return from join() on it, so the parent sees everything the worker wrote.

open as a page

The Java Memory Model defines correctness through a partial order of happens-before edges rather than by listing which reorderings a compiler or CPU may perform. What does that design choice buy, and what does it cost the people writing Java?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

A partial order is portable and implementation-neutral: it constrains observable outcomes, not instruction schedules, so every JVM, JIT and CPU can optimize freely while programs stay correct everywhere. The cost is that correctness becomes non-observable — races are invisible in testing and must be reasoned about.

open as a page