How the Java Memory Model Works

Advanced
8 min read· Backend & Databases

The Java Memory Model (JMM) defines when a value written by one thread is guaranteed to be visible to another. Modern CPUs and compilers reorder and cache operations for speed, so without rules a thread could see stale or half-constructed data. The JMM gives you the happens-before relationship: a set of guarantees (via volatile, synchronized, locks, and thread lifecycle) that establish ordering and visibility so concurrent programs behave predictably.

Think of threads as writers with private notebooks

Each thread keeps a private notebook (CPU cache) and only occasionally copies notes to the shared whiteboard (main memory). If one writer jots down a value but never copies it to the board, others keep reading the old value. A volatile write is a promise to immediately publish to the board and a volatile read is a promise to read fresh from it. happens-before is the office rule that says: once I publish, and you read after, you are guaranteed to see what I wrote.

Step by Step

1 / 5

Key Concepts

happens-before

The JMM guarantee that if A happens-before B, everything A did is visible to B. It is the formal foundation for reasoning about visibility and ordering in concurrent Java.

Visibility

Whether a write by one thread can be seen by another. Without synchronization or volatile, there is no guarantee — a reader may see an arbitrarily stale value.

Memory Barrier

A low-level instruction that prevents reordering across it and forces caches to sync with main memory. volatile, locks, and final-field freezes emit barriers under the hood.

Safe Publication

Making a fully-constructed object visible to other threads correctly — via final fields, volatile, locks, or concurrent collections — so no thread observes a partially built object.

Key Facts

  • final fields have special JMM guarantees: once a constructor finishes, other threads that see the object see its final fields fully initialised.
  • The infamous "broken" double-checked locking works correctly only if the singleton field is declared volatile.
  • Data races are not just bugs — they make program behaviour undefined by the JMM, so results can vary by CPU, JVM, and even run.

Real-World Applications

A stop flag for a worker thread

Declaring the running boolean volatile guarantees the worker sees the stop signal promptly. Without volatile, the loop can run forever because the reader caches the old value.

Lazy singleton initialisation

The holder-class idiom or a volatile field with double-checked locking guarantees threads never see a half-constructed singleton — a direct application of safe publication rules.

Frequently Asked Questions

Why does my thread not see another thread update?

Because of caching and reordering, a write is not guaranteed visible without a happens-before relationship. Make the shared field volatile, or guard access with the same lock, and the reader will see the latest value.

Does volatile make operations atomic?

No. volatile guarantees visibility and ordering for a single read or write, but count++ is still three steps and can race. For atomic compound updates use AtomicInteger or a lock.

What is a data race?

Two threads access the same variable, at least one writes, and there is no happens-before ordering between them. The JMM leaves the outcome undefined, so a data race can produce stale reads, torn values, or reordered effects.

How do final fields help concurrency?

The JMM guarantees that if an object is properly constructed (the reference does not escape during construction), any thread that sees the object sees its final fields correctly initialised — enabling safe sharing of immutable objects without synchronization.

Related Topics