---
title: 'Java Memory Model'
source: 'https://academia.sh/en/courses/jvm-and-concurrency/java-memory-model'
course: 'JVM, Concurrency and Performance'
language: en
updated: '2026-09-13T05:09:02+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Builds the happens-before relation as a concrete list of rules and writes down which edge each mechanism establishes'
    - 'Counts eight publication mechanisms by whether the writer, the reader, or both must act'
    - 'Explains, with a modeled setup rather than a run, why a visibility defect cannot be shown by running it'
---

# Java Memory Model

Of eight publication mechanisms, four ask both the writer and the reader to act, one puts the load on the writer alone (a final field), one on the reader alone (join), and two never ask the visibility question at all. The visibility defect of a field without synchronization is not shown by running it; it is modeled as a rule.

The previous lesson worked entirely with setups where the same thread both wrote and read a
value; `start.await()` and `t.join()` only established **order**, and the mechanism providing
visibility was already there without ever being named. This lesson names that mechanism. When
one thread writes a value and another thread reads it, the reader seeing the written value is
**not automatic** — what makes it visible is that a sequencing the language recognizes has been
established between the two actions. This sequencing is called the **happens-before relation**,
and the question this lesson asks the course is: in each of eight distinct publication
mechanisms, **which side** has to do something to establish this relation — only the writer,
only the reader, both, or neither?

The "not automatic" part of the question may look like it contradicts the previous lesson's
examples, where every value was read correctly without any extra effort. The contradiction is
only on the surface: every setup in the previous lesson already used a `CountDownLatch` or
`join`, and these two tools — as will be shown below — are themselves happens-before sources in
their own right. Visibility was not automatic there either; the necessary relation was already
established on its own, and this lesson is naming why that establishment was sufficient.

## Happens-Before as a Rule

- **CC5** — The happens-before relation is not a timing claim; it does not say **in what order**
  two actions occurred, it only guarantees that once one has occurred, the results of the other
  become **visible**. This relation is never measured anywhere in this lesson; it is only used
  as a rule.

Among the happens-before rules the language recognizes, the ones used in this lesson are these:
actions within a single thread happen-before each other in program order; releasing a monitor
lock happens-before every later action that acquires the same lock; writing to a `volatile`
field happens-before every later action that reads that same field; starting a thread
(`start()`) happens-before every action within that thread; every action within a thread
happens-before another thread successfully returning from a `join()` on that thread; and the
value of a `final` field in the constructor happens-before every thread that reaches that object
by **safe means** after the constructor completes. This list is enough to categorize the eight
mechanisms below.

All of these rules are **transitive**: if action A happens-before B and B happens-before C, then
A also happens-before C — even if the link in between uses a different mechanism. So in a chain
where a value is written in `main`, a thread is `start()`ed, and inside that thread the value is
passed to a second thread with a `CountDownLatch`, the write at the very start reaches the read
at the very end **not in a single jump, but link by link**; each link applies its own rule, and
the chain as a whole is not separately proven. Each of the measurements below isolates **a
single link** of that chain — the direct bond between a writer and a reader.

## The Writer's Cost: final Fields and Safe Publication

- **CC6** — The measurement places no extra step in the reader's code flow; the reader only
  reads the field. The entire cost is placed on the moment the constructor completes.

```java
// SafePublication.java — the cost of a final field is paid at construction, the reader does nothing extra
public class SafePublication {
    static final class Packet {
        final int value;
        Packet(int value) { this.value = value; }
    }

    public static void main(String[] args) throws InterruptedException {
        Packet box = new Packet(5);
        Thread reader = new Thread(() -> {
            System.out.println("reader adds nothing, just reads Packet.value: " + box.value);
        });
        reader.start();
        reader.join();
    }
}
```

```
reader adds nothing, just reads Packet.value: 5
```

Until the `Packet` constructor completes, the `value` field is considered unwritten; the moment
the constructor finishes, this field is **frozen**. The `reader` thread reading `box.value` does
not take a lock or check a volatile field — it applies no extra discipline at all, because the
discipline it would need was already spent **on the writer's side**, inside the constructor. In
this lesson, the `box` reference reaches `reader` through `start()`, which is already a channel
that establishes a happens-before relation on its own; the `final` field's own guarantee is
broader than that — as long as the constructor does not let `this` escape early, the field's
correct value is seen no matter which channel the reference arrives through. This broader
guarantee is not run separately here: proving it would require **deliberately** publishing the
reference without a happens-before link, and that is exactly the kind of setup the limiting
measurement below avoids.

The guarantee also has a condition: the constructor must **not let `this` escape early**. If a
constructor writes `this` to a static field or registers it with a listener before its own
completion, another thread can see that object while the constructor is **still running** — and
at that moment the `final` field may be unassigned or half-assigned. This lesson does not set up
such an escape; it only records that the `final` field's guarantee is **not unconditional**, and
depends on the constructor's own discipline. `SafePublication` satisfies this condition:
`Packet`'s constructor never writes `this` anywhere, and the constructor finishes right after
`value` is assigned.

## Eight Mechanisms, Four Categories

- **CC7** — The six mechanisms below are measured with a bounded loop called `waitUntil`: the
  loop condition is tested up to two hundred million times until it becomes true, and the
  duration is never printed anywhere. The only thing measured is whether the condition
  **became true or not**, and the value read when it does.

```java
// Publication.java — is it enough for the writer and reader to use the same mechanism, across six setups
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;

public class Publication {
    static volatile int volatileValue = 0;
    static volatile boolean volatileReady = false;

    static final Object lock = new Object();
    static int lockedValue = 0;
    static boolean lockedReady = false;

    static final AtomicInteger atomicValue = new AtomicInteger(-1);

    static int joinValue = 0;
    static int latchValue = 0;

    static final ThreadLocal<Integer> own = ThreadLocal.withInitial(() -> 0);

    static boolean waitUntil(BooleanSupplier condition) {
        for (long i = 0; i < 200_000_000L; i++) if (condition.getAsBoolean()) return true;
        return false;
    }

    public static void main(String[] args) throws InterruptedException {
        // 1. volatile: the writer writes a volatile field, the reader reads the same volatile field
        Thread t1 = new Thread(() -> { volatileValue = 5; volatileReady = true; });
        t1.start();
        boolean g1 = waitUntil(() -> volatileReady);
        System.out.println("volatile       : seen=" + g1 + " value=" + volatileValue);
        t1.join();

        // 2. synchronized: the writer and reader use the same lock
        Thread t2 = new Thread(() -> { synchronized (lock) { lockedValue = 5; lockedReady = true; } });
        t2.start();
        boolean g2 = waitUntil(() -> { synchronized (lock) { return lockedReady; } });
        int d2; synchronized (lock) { d2 = lockedValue; }
        System.out.println("synchronized   : seen=" + g2 + " value=" + d2);
        t2.join();

        // 3. AtomicInteger: the writer calls set, the reader calls get
        Thread t3 = new Thread(() -> atomicValue.set(5));
        t3.start();
        boolean g3 = waitUntil(() -> atomicValue.get() != -1);
        System.out.println("AtomicInteger  : seen=" + g3 + " value=" + atomicValue.get());
        t3.join();

        // 4. join: the body finishes, the reader reads only after join — plain field, nothing else
        Thread t4 = new Thread(() -> joinValue = 5);
        t4.start();
        t4.join();
        System.out.println("join           : seen=true value=" + joinValue);

        // 5. CountDownLatch: the writer calls countDown, the reader calls await — plain field
        CountDownLatch latch = new CountDownLatch(1);
        Thread t5 = new Thread(() -> { latchValue = 5; latch.countDown(); });
        t5.start();
        latch.await();
        System.out.println("CountDownLatch : seen=true value=" + latchValue);
        t5.join();

        // 6. ThreadLocal: every thread sees its own copy, never the other thread's at all
        Thread t6 = new Thread(() -> own.set(5));
        t6.start();
        t6.join();
        System.out.println("ThreadLocal    : caller's own copy=" + own.get()
                + " (the 5 written by the created thread is not in this copy at all)");
    }
}
```

```
volatile       : seen=true value=5
synchronized   : seen=true value=5
AtomicInteger  : seen=true value=5
join           : seen=true value=5
CountDownLatch : seen=true value=5
ThreadLocal    : caller's own copy=0 (the 5 written by the created thread is not in this copy at all)
```

All six lines read the same way, but their justifications differ. For **`volatile`** and
**`synchronized`**, both the writer and the reader have to touch the same field or the same
lock — if the reader checked a different field or a different lock, no guarantee would remain at
all; these two are written into the **both** column. **`AtomicInteger`** is a third example for
the same reason: the writer calls `set`, the reader calls `get`, both through the same object.
**`CountDownLatch`** is the same again — the writer must call `countDown`, the reader must call
`await`; if either one were missing, the relation would not be established. **`join`** carries a
different load: there is **nothing extra** the writer's side has to do, it only has to finish
normally; the entire load falls on the reader calling `join()` — which is why it is in the
**reader only** column. A `final` field is the opposite: the entire load is on the constructor
completing, i.e. in the **writer only** column. **`ThreadLocal`** never asks the visibility
question at all — since every thread has its own copy, there is **no** expectation that the
writer and reader be visible to each other, which is why it goes in the **neither** column. The
other seven were ways of making a value safe by **sharing** it; `ThreadLocal` instead turns not
sharing itself into the tool — the same-named variable living two separate lives in two threads
does not solve the visibility question, it removes it from the question entirely.

| Category | Mechanisms | Count |
|---|---|---|
| writer only | `final` field | 1 |
| reader only | `join` | 1 |
| both | `volatile`, `synchronized`, `AtomicInteger`, `CountDownLatch` | 4 |
| neither | field without synchronization, `ThreadLocal` | 2 |

**Four** of the eight mechanisms ask both sides to act, **one** puts the load solely on the
writer, **one** solely on the reader, and **two** fall outside the visibility question — one
because no rule establishes it, the other because it is isolated enough that no rule needs to.

Placed side by side, `join` and `CountDownLatch` look at first glance like they do the same job,
but their places in the table differ — one is in **reader only**, the other in **both**. The
distinction comes from this: the happens-before relation `join` establishes is tied to the
thread's **own natural end** — the writer's side has no extra method it calls, the relation is
established however the body finishes. `CountDownLatch` is not tied to such a natural end — the
count can drop either before or after the body finishes, through the writer's side **explicitly**
calling `countDown`. This difference makes `CountDownLatch` more flexible than `join` (a thread
can signal progress before it finishes), but the price of that flexibility is one extra call
placed on the writer's side.

## Limiting Measurement: Two Modeled Limits

- **CC8** — No code is run in this section. Both claims are supported by reference to the rule
  and by citing earlier courses, not by measurement — because measuring it would either be
  misleading or impossible by definition.

The first item in the table's `neither` row, an ordinary field without synchronization, is
deliberately not run. This is not from laziness, it is a limit: a thread writing an
unsynchronized field and another thread reading it in a loop can, even on the same program on
the same machine, sometimes show the visibility right away and sometimes not — the result
depends on whatever optimization decision the compiler happens to make at that moment, and this
lesson does not measure such a decision, because that is not M08/K04's own measurement axis.
Running such a loop here and writing "it did not show up" or "it showed up" would be presenting a
one-time observation as a general rule; but the rule is already established above, and **no
item covers this field.** The correct reading is: if a field does not fall under one of the
seven mechanisms in this list, there is no guarantee at all about that field's visibility —
neither "visible" nor "invisible" is guaranteed, only **not guaranteed.**

This means a short-lived trial could **always** show visibility and mislead the programmer. The
reason the `waitUntil` loop in `Publication` can remain a genuine test is that all three fields
it checks (`volatileReady`, `lockedReady`, `atomicValue`) are covered by a rule; if the same loop
checked an unsynchronized `boolean` field, it would probably still print `seen=true` — because in
a short-lived program, the compiler may not yet have had the opportunity to reorder the loop.
This apparent success is not proof; it only means that in that particular run, no price was
paid for breaking the rule. The same decision measured in the Just-in-Time Compilation lesson
applies here too: the same source, unchanged, can behave differently depending on a runtime
decision — the difference is that lesson measured the decision's instruction count, this lesson
only points out that the decision can break **observability**.

The second limit concerns the `volatile` field, and it is the one most readily confused with the
visibility guarantee being measured. What `volatile` gives is **visibility and ordering**, not
**atomicity** itself. The read-increment-write trio was established as a concept in the M01/K05
Operating System Concepts course; the same trio can also be split on a `volatile` field — when
two threads both write `counter++` to the same `volatile` counter, even though both sides do see
the operation, one can perform its own increment before it has read the other's increment, and
an increment gets lost. This lesson does not **run** a real race between these two sides,
because the result would be environment-dependent, and how many times the lost increment
happens is not tied anywhere to this lesson's measurement axis. The claim remains a rule: a
`volatile` gives visibility for a single read or a single write; it gives nothing for an
operation that combines a read and a write. The actual tool for that is
**compare-and-set**, on `AtomicInteger`, measured in the next lesson.

## Summary

- Visibility is not automatic; a write becoming visible to a read depends on a happens-before
  relation the language recognizes having been established between the two.
- Of eight publication mechanisms, **four** (`volatile`, `synchronized`, `AtomicInteger`,
  `CountDownLatch`) ask both the writer and the reader to act, **one** (a `final` field) puts
  the load solely on the writer, **one** (`join`) puts the load solely on the reader, and
  **two** (a field without synchronization, `ThreadLocal`) fall outside the visibility question.
- The cost of a `final` field is paid at construction: once the constructor completes, the
  reader sees the correct value without applying any extra discipline.
- The visibility defect of a field without synchronization is not shown by running it; this
  lesson deliberately models it, because the result would be environment-dependent.
- `volatile` gives visibility and ordering, not atomicity; the read-increment-write trio can
  still be split on a `volatile` field, and this lesson explains that as a rule, not by running
  it.

## Next Step

In this lesson, the `synchronized` block entered the table as just one row, and it was said
that the reader and the writer need to use the **same** lock — but the phrase "same lock" was
never questioned itself; `lock` here was a single static field, and the two threads using the
same object was given from the start. The next lesson removes that assumption and measures who
a monitor lock belongs to: when the same synchronized method is called on two separate
instances, how many separate locks come into play, and how many times is it counted when a
thread re-enters a lock it already holds?
