Java Memory Model and Happens-Before
The Java Memory Model (JMM) is the part of the Java specification that answers one question: when is a value written by one thread guaranteed to be seen by another thread? Compilers and CPUs reorder and cache memory operations to run faster, which is invisible within one thread but can surprise other threads. The JMM defines a relation called happens-before: if action A happens-before action B, then B is guaranteed to see the effects of A. In this lesson, you will learn why such rules are needed and the happens-before rules you use every day.
Why a Memory Model Is Needed
Within one thread, Java guarantees that code behaves as if it ran in program order. To be fast, though, the JIT compiler, the CPU and its caches may:
- Reorder independent reads and writes,
- Keep values in registers or caches instead of main memory,
- Remove reads they think are repeated.
Other threads can observe these effects. The JMM says exactly which orderings and values another thread is allowed to see, and gives you tools to forbid surprises.
The Happens-Before Rules
If A happens-before B, then all writes made before A are visible to B, and they aren't reordered across that edge.
| Rule | Happens-before edge |
|---|---|
| Program order | Each action in a thread happens-before every later action in the same thread |
| Monitor lock | Releasing a lock happens-before every later acquire of the same lock |
| Volatile | A write to a volatile field happens-before every later read of that field |
| Thread start | t.start() happens-before every action in thread t |
| Thread join | Every action in t happens-before another thread returns from t.join() |
| Interruption | t.interrupt() happens-before t detects the interrupt |
| Transitivity | If A happens-before B and B happens-before C, then A happens-before C |
| Final fields | A properly constructed object's final fields are visible to all threads |
Example: Publishing Data Safely
Let's see an example that uses a volatile flag to publish ordinary data from one thread to another.
public class Publish {
static int[] marks;
static String owner;
static volatile boolean ready = false;
public static void main(String[] args) throws InterruptedException {
Thread reader = new Thread(() -> {
while (!ready) {
Thread.onSpinWait();
}
System.out.println(owner + ": " + marks[0] + ", " + marks[1]);
});
reader.start();
marks = new int[] {91, 85};
owner = "Navika";
ready = true;
reader.join();
}
}Navika: 91, 85Explanation: marks and owner are plain fields, but they are written before the volatile write ready = true. The reader reads ready (volatile) before reading them. By program order, the volatile rule and transitivity, everything written before ready = true is visible after the reader sees true. Without volatile, the reader could loop forever or even see ready as true but owner still null.
Everyday Ways to Get Happens-Before
- Guard shared data with the same lock for every read and write.
- Use
volatilefor flags and for publishing references. - Start threads after setting up their data; join before reading their results.
- Use
java.util.concurrentclasses (queues, latches, atomics,ConcurrentHashMap): their methods create happens-before edges between the threads that use them. - Make objects immutable, with
finalfields.
Common Mistakes
Assuming a write is visible because it happened "earlier" in real time. Without a happens-before edge, it may not be.
Using different locks for writes and reads. Only the same lock creates the edge.
Letting this escape from a constructor. Other threads may see the object before its fields are set.
Interview Corner
What is happens-before in the Java Memory Model?
Happens-before is a partial ordering defined by the JMM: if action A happens-before action B, the results of A, and of everything that happened-before A, are visible to B and won't be reordered after it. Edges are created by program order within a thread, releasing a monitor before a later acquire of it, a volatile write before a later read of it, Thread.start before the started thread's actions, a thread's actions before another's return from join, and transitivity. Data races occur when conflicting accesses aren't ordered by happens-before.
Practice
- Easy: For the
JoinResultexample in "Joining Threads in Java", name the happens-before rule that makes the worker's result visible tomain. - Harder: Remove
volatilefromreadyinPublish, run it several times, and explain why the result is no longer guaranteed even if it seems to work.
Key Takeaways
Was this tutorial helpful?