Fundamentals 13 min read

Java Happens-Before: Visibility Guarantees Beyond Temporal Order

This article demystifies Java's happens-before rules, showing why temporal order doesn't guarantee visibility, and how volatile combined with program order ensures a non-volatile variable's visibility across threads through a concrete code example.

samdeepthink
samdeepthink
samdeepthink
Java Happens-Before: Visibility Guarantees Beyond Temporal Order

Understanding Happens-Before in Java

The article opens with a demo run on Windows 11 using Java 17. Two threads share an int x = 0 and a volatile boolean flag = false. Thread A writes x = 42 then flag = true; Thread B reads flag and, if true, prints x. Over 10 iterations the output is consistently flag=true, x=42, even though x has no synchronization. The author explains that the Java Memory Model (JMM) guarantees: if Thread B sees flag == true, then x must be 42.

Why Happens-Before Is Not Temporal Order

The term "happens-before" is often misunderstood as "occurs earlier in time." The author clarifies with two key points:

Temporal precedence does not guarantee visibility. A write at second 1 may still sit in a CPU cache when a read occurs at second 2, so the read can see the stale value 0.

Happens-before does not require physical execution order. The JLS states that as long as the reordered execution yields a result consistent with some legal sequential execution, the reordering is allowed. Example: in a single thread, a = 1 happens-before b = 2, but the compiler/CPU may swap them because they are independent; the observable result is unchanged.

Thus happens-before is a visibility and ordering promise , not a timestamp.

Why We Need Happens-Before: Hardware/Compiler vs. Programmer

Two conflicting demands drive the JMM:

Demand A (hardware/compiler): Performance via (1) compiler instruction reordering to keep registers hot, (2) CPU out-of-order execution (pipelining), and (3) delayed cache synchronization across cores.

Demand B (programmer): Deterministic, readable, predictable behavior.

The JMM acts as mediator: when programmers use volatile, locks, Thread.start(), Thread.join(), etc., the JMM establishes happens-before edges that guarantee visibility; without such edges, a data race exists and any read result is legal.

Common Happens-Before Rules

Program Order Rule: In a single thread, each action happens-before the next in program order (guarantees result equivalence, not physical order).

Monitor Lock Rule: Unlocking a monitor happens-before a subsequent lock on the same monitor.

Volatile Variable Rule: A write to a volatile variable happens-before a subsequent read of that same volatile variable.

Thread Start Rule: Actions before Thread.start() in the parent thread happen-before any action in the started thread.

Thread Join Rule: All actions in a thread happen-before another thread returns from join() on that thread.

Transitivity: If A happens-before B and B happens-before C, then A happens-before C.

Re-analyzing the Demo with Happens-Before Chain

The four operations are:

Thread A: x = 42 (plain write)

Thread A: flag = true (volatile write)

Thread B: if (flag) (volatile read)

Thread B: System.out.println(x) (plain read)

Applying the rules:

Program order in Thread A: operation 1 happens-before operation 2.

Volatile rule: operation 2 happens-before operation 3 (provided operation 3 reads the value written by operation 2).

Program order in Thread B: operation 3 happens-before operation 4.

Transitivity: operation 1 happens-before operation 4.

Therefore, when Thread B executes operation 4, the write x = 42 is guaranteed visible, even though x is not volatile.

The Critical Condition: Reading true

The entire chain is conditional on operation 3 actually reading true. Because both threads start simultaneously, Thread B may read flag before Thread A writes true, seeing the initial false. In that case the if block is skipped, x is never read, and no happens-before guarantee is activated. The occasional false outputs observed in repeated runs are due to this scheduling race, not a visibility failure. volatile ensures visibility after the write; it cannot prevent a read from occurring before the write.

Summary

Do not reason with wall-clock time in concurrent programs; reason with happens-before edges.

If a happens-before chain exists between a write and a read, the JMM guarantees visibility regardless of CPU architecture (x86, ARM, etc.).

Without a chain, the situation is a data race and the read result is unspecified.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaConcurrencyvolatilememory modelJMMHappens-Beforethread synchronizationJava 17
samdeepthink
Written by

samdeepthink

Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.