W3docs

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 unseen

The 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:

RuleHappens-before edge
Program orderEach action in a thread happens-before later actions in that same thread
Monitor lockUnlocking a monitor happens-before a later lock of the same monitor
VolatileA write to a volatile field happens-before every later read of it
Thread startthread.start() happens-before any action in the started thread
Thread joinAll actions in a thread happen-before another thread returning from its join()
Final fieldsA 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 here

Atomicity 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.

ProblemSymptomFix
VisibilityA thread never sees an updated valuevolatile, synchronized, final
AtomicityLost updates from x++ under contentionsynchronized, AtomicInteger, locks
ReorderingOperations appear out of orderhappens-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.

java— editable, runs on the server

What to take from the run:

  • reader saw data = 42 proves the volatile write to flag published the plain write to data — the reader is guaranteed to see it because of the happens-before edge.
  • atomic counter = 800000 (expected 800000) shows AtomicInteger.incrementAndGet() lost no updates across 8 threads doing 100,000 increments each — a plain int++ would print a smaller, non-deterministic number.
  • final config = prod:443 demonstrates safe publication: the final fields of Config are correct without any volatile or lock.
  • synchronized sum = 10000 confirms 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

Practice

What guarantee does declaring a field 'volatile' provide in the Java Memory Model?