Java Memory Model
The Java Memory Model — what reads and writes are visible across threads and how happens-before works.
Java Memory Model
The Java Memory Model (JMM) is the part of the language specification that defines when one thread is guaranteed to see another thread's writes. It is the rulebook behind volatile, synchronized, and final — and the reason correct multithreaded code looks the way it does.
Why the Memory Model Exists
On modern hardware, the value a thread "writes" to a field may sit in a CPU register or core-local cache long before it reaches main memory, and the compiler is free to reorder independent instructions. Without rules, one thread could set a field while another thread never sees the change — or sees it out of order.
The JMM defines a single guarantee that tames all of this: the happens-before relationship. If action A happens-before action B, then A's effects are visible to B. Everything else — volatile, locks, final, thread starts and joins — is just a way to create a happens-before edge.
// Without synchronization, this loop may NEVER terminate:
// the reader thread can cache 'running' forever and miss the write.
static boolean running = true; // plain field — no guarantee
void reader() { while (running) { /* spin */ } } // may hang
void stopper() { running = false; } // may go unseenThe volatile Keyword
Declaring a field volatile does two things: every read goes to main memory (visibility), and a volatile write happens-before every later volatile read of the same field (ordering). It does not make compound actions like count++ atomic.
public class Worker {
private volatile boolean running = true; // visible across threads
public void run() {
while (running) { // always sees the latest value
doWork();
}
}
public void stop() {
running = false; // guaranteed visible to run()
}
}Use volatile for a single flag or a reference that is read by many threads and written by one. Reach for it when you need visibility, not mutual exclusion.
Happens-Before: The Core Rules
Happens-before is the contract you actually program against. These edges are the ones you create on purpose:
| Rule | Happens-before edge |
|---|---|
| Program order | Each action in a thread happens-before later actions in that same thread |
| Monitor lock | Unlocking a monitor happens-before a later lock of the same monitor |
| Volatile | A write to a volatile field happens-before every later read of it |
| Thread start | thread.start() happens-before any action in the started thread |
| Thread join | All actions in a thread happen-before another thread returning from its join() |
| Final fields | A constructor's writes to final fields happen-before the object is published |
// synchronized creates a happens-before edge through the same lock:
synchronized (lock) { shared = compute(); } // unlock here ...
// ... happens-before another thread's:
synchronized (lock) { use(shared); } // ... lock hereAtomicity vs. Visibility
These are two different problems and they need different tools. volatile fixes visibility but not atomicity; synchronized and the java.util.concurrent.atomic classes fix both for the section they cover.
| Problem | Symptom | Fix |
|---|---|---|
| Visibility | A thread never sees an updated value | volatile, synchronized, final |
| Atomicity | Lost updates from x++ under contention | synchronized, AtomicInteger, locks |
| Reordering | Operations appear out of order | happens-before via the tools above |
import java.util.concurrent.atomic.AtomicLong;
public class Counter {
private final AtomicLong hits = new AtomicLong();
public void record() { hits.incrementAndGet(); } // atomic + visible
public long total() { return hits.get(); }
}final Fields and Safe Publication
A final field set in the constructor is frozen by the time the constructor returns. Any thread that sees a properly constructed object (one whose reference did not leak from the constructor) is guaranteed to see correct values for its final fields — no volatile or lock required. This is why immutable objects are inherently thread-safe.
public final class Point {
private final int x, y; // frozen at construction
public Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
}
// Share a Point across threads freely: its final fields are safely published.A Self-Contained Example
The runnable example below uses only the JDK. It exercises four memory-model tools in one program: volatile for cross-thread visibility, AtomicInteger for lost-update-free counting, final fields for safe publication, and synchronized for atomic accumulation.
What to take from the run:
reader saw data = 42proves thevolatilewrite toflagpublished the plain write todata— the reader is guaranteed to see it because of the happens-before edge.atomic counter = 800000 (expected 800000)showsAtomicInteger.incrementAndGet()lost no updates across 8 threads doing 100,000 increments each — a plainint++would print a smaller, non-deterministic number.final config = prod:443demonstrates safe publication: thefinalfields ofConfigare correct without anyvolatileor lock.synchronized sum = 10000confirms the four writers (1000+2000+3000+4000) accumulated through the same monitor with no lost additions.- Each output line corresponds to a different happens-before mechanism, yet they compose in one program — the JMM tools are complementary, not interchangeable.
Practice
What guarantee does declaring a field 'volatile' provide in the Java Memory Model?