Definition
The Java Memory Model (JMM) defines which executions and outcomes are legal for a Java program with multiple threads.
Without synchronization, Java does not guarantee that other threads observe writes in source-code order. The outcome is not literally “anything”, but it can still be very surprising.
Why JMM matters
- It gives a formal meaning to
volatile,synchronized, threadstart(), andjoin(). - It explains visibility and ordering between threads.
- It still allows data races: a racy read may observe a write that is not the latest one in wall-clock time.
Actions and executions
The JMM models a run as actions plus ordering relations between them.
Examples of actions:
read(x): 1means “read variablex, and the value observed is1”write(x): 1- volatile read / volatile write
- monitor lock / unlock
Example:
int x = 0;
// Thread 1
x = 1;
// Thread 2
int r = x;One possible execution contains:
write(x): 1read(x): 1
Another legal execution may contain:
write(x): 1read(x): 0
if there is no synchronization between the threads.
Program Order (PO)
Program order is the total order of actions inside one thread.
// Thread 1
x = 1;
y = 1;Within Thread 1, write(x) is before write(y) in program order.
But PO says nothing about other threads:
// Thread 1
x = 1;
y = 1;
// Thread 2
int r1 = y;
int r2 = x;Thread 2 may still see r1 == 1 and r2 == 0, because PO is only intra-thread. It links executions back to the original code, but it does not by itself guarantee cross-thread visibility.
Synchronization Actions (SA) and Synchronization Order (SO)
Some actions are special: they participate in synchronization.
Synchronization actions include:
- read/write of a
volatilevariable lock/unlockof a monitor- first and last action of a thread (synthetic)
- actions that start a thread
- actions that detect thread termination, such as
join()
These actions form the synchronization order (SO):
- SO is a total order
- all threads agree on that order
- synchronization actions inside one thread remain in PO order
Example with volatile:
volatile int v = 0;
// Thread 1
v = 1;
// Thread 2
int r = v;The volatile write and volatile read are synchronization actions. The JMM places them in the single global synchronization order.
Synchronizes-With (SW)
Synchronizes-with connects specific synchronization actions that “see” each other.
Important cases:
- a
volatilewrite synchronizes-with a latervolatileread of the same variable - a monitor
unlocksynchronizes-with a laterlockof the same monitor Thread.start()synchronizes-with the started thread’s first action- a thread’s last action synchronizes-with another thread successfully returning from
join()
Example with volatile:
int data = 0;
volatile boolean ready = false;
// Thread 1
data = 42;
ready = true;
// Thread 2
if (ready) {
int r = data; // guaranteed to see 42
}Why? The write ready = true synchronizes-with the read that sees ready == true. That makes the earlier write to data visible as well.
Happens-Before (HB)
The most useful relation is happens-before.
- PO edges are part of HB
- SW edges are part of HB
- HB is the transitive closure of PO and SW
So if:
Ais beforeBin program order, andBsynchronizes-withC
then A happens-before C.
Example with synchronized:
int value = 0;
final Object lock = new Object();
// Thread 1
synchronized (lock) {
value = 7;
}
// Thread 2
synchronized (lock) {
int r = value; // guaranteed to see 7
}The unlock in Thread 1 synchronizes-with the later lock in Thread 2, so the write value = 7 happens-before the read.
HB consistency
When a thread reads a variable, it may see:
- the last write ordered before it by HB, or
- another write that is not ordered with it
This is why Java still allows races.
Example:
int x = 0;
// Thread 1
x = 1;
// Thread 2
x = 2;
// Thread 3
int r = x;If there is no HB relation, r may legally become 0, 1, or 2.
Warning
If two conflicting accesses are not ordered by happens-before, you have a data race. The program is still defined by the JMM, but the result may be unexpected.
Minimal rules to remember
- PO: order inside one thread only
- SO: one global order for synchronization actions
- SW: specific synchronization edges, such as volatile-write to volatile-read
- HB: the order that actually matters for visibility