# Java Memory Model Explained: How the volatile Keyword Controls Visibility and Ordering

> Understand the Java Memory Model (JMM) and how the volatile keyword ensures thread visibility and prevents reordering. Learn about memory barriers and main memory writes.

- Repository: [CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes)
- Tags: deep-dive
- Published: 2026-02-24

---

**The Java Memory Model (JMM) defines the rules for how threads interact through memory, and the volatile keyword ensures cross-thread visibility and prevents instruction reordering by inserting memory barriers that flush writes to main memory.**

The Java Memory Model is the formal specification that governs thread communication and memory consistency in Java applications. According to the CyC2018/CS-Notes repository, which documents these concurrency internals in `notes/Java 并发.md`, the JMM abstracts away hardware and operating system details to guarantee consistent program behavior across all platforms.

## What Is the Java Memory Model?

The **Java Memory Model** specifies how and when changes made by one thread become visible to other threads. It creates a layer of abstraction between the Java program and the underlying CPU architecture, hiding differences between processor caches and memory hierarchies.

### Main Memory vs. Working Memory

In the JMM architecture, all variables reside in **main memory** (shared by all threads). However, each thread maintains its own **working memory** (local CPU caches or registers) that holds copies of the variables it reads. Threads cannot directly read or write main memory; instead, they operate on their working memory copies, and all value exchanges must synchronize through main memory.

As detailed in `notes/Java 并发.md`, this two-tiered structure is fundamental to understanding race conditions and visibility issues in concurrent programs.

### Memory-Interaction Operations

The JMM defines eight low-level primitive actions that move data between main memory and working memory:

1. `lock` – Acquire a monitor lock
2. `unlock` – Release a monitor lock
3. `read` – Transfer a value from main memory to a thread's working memory (for `load`)
4. `load` – Place the `read` value into a working memory variable
5. `use` – Transfer a value from working memory to the JVM execution engine
6. `assign` – Receive a value from the execution engine into working memory
7. `store` – Transfer a value from working memory to main memory (for `write`)
8. `write` – Place the `stored` value into a main memory variable

These eight actions form the foundation of all thread-memory interactions in Java.

### The Three Core Guarantees

The JMM provides three fundamental guarantees that govern concurrent execution:

- **Atomicity**: Each of the eight memory operations is indivisible. However, for non-volatile 64-bit types (`long` and `double`), the JVM may split a single write or read into two 32-bit operations, breaking atomicity unless the variable is declared `volatile`.

- **Visibility**: A write to a variable is ultimately flushed to main memory, and a read obtains the latest value from main memory. Without proper synchronization, threads may see stale cached values.

- **Ordering**: The JVM and CPU may reorder instructions for performance unless a **happens-before** relationship (established via `volatile`, `synchronized`, or other synchronization mechanisms) forces a specific execution order.

## How the volatile Keyword Affects the JMM

The `volatile` keyword is a field modifier that directly influences the **visibility** and **ordering** guarantees of the Java Memory Model. When applied to a variable, it changes how the JVM handles memory barriers and cache coherence.

### Visibility Guarantees

Every write to a `volatile` variable is immediately flushed to main memory, bypassing the normal delay that might keep values in a thread's working memory. Correspondingly, every read of a `volatile` variable loads the latest value directly from main memory rather than a potentially stale local cache.

As implemented in the JVM according to `notes/Java 并发.md`, this ensures that once a thread writes to a volatile field, all other threads will see that update immediately.

### Memory Barriers and Instruction Ordering

To enforce ordering, the JVM inserts specific **memory barriers** around volatile operations:

- A **store barrier** is inserted after a write to a `volatile` field. This barrier blocks the compiler and CPU from moving subsequent write operations before the volatile write.
- A **load barrier** is inserted before a read of a `volatile` field. This prevents earlier read operations from being reordered after the volatile read.

These barriers prevent instruction reordering across the volatile access point, ensuring that memory actions before the write (or after the read) cannot migrate past the volatile boundary.

### The Happens-Before Relationship

The JMM defines a specific **happens-before** rule for volatile variables: a write to a `volatile` field *happens-before* every subsequent read of that same field. This creates a partial ordering of operations that developers can rely on for safe publication of immutable objects or lightweight synchronization between threads.

This relationship is the formal mechanism that allows volatile to work correctly for flags and status indicators without the full overhead of `synchronized` blocks.

### Limitations of volatile

`volatile` does **not** provide atomicity for compound actions. While it guarantees that individual reads and writes are visible and ordered, it does not make operations like `count++` atomic. A `count++` operation compiles to a read-modify-write sequence, and without additional synchronization, race conditions can still occur between threads.

## Practical Code Examples

The following examples demonstrate correct and incorrect uses of `volatile` based on the JMM specification.

### Safe Publication with volatile

Use `volatile` to ensure other threads see a fully initialized object:

```java
class Holder {
    // volatile guarantees other threads see a fully-initialized object
    private volatile Data data;

    void publish(Data d) {
        data = d;          // (1) write volatile – store barrier flushes to main memory
    }

    Data consume() {
        return data;       // (2) read volatile – load barrier fetches from main memory
    }
}

```

### Incorrect Use for Atomic Increment

Do not use `volatile` when you need atomic compound operations:

```java
class Counter {
    private volatile int cnt = 0; // visibility only, not atomicity

    void inc() {
        cnt++; // compiled to read-modify-write; race condition remains possible
    }
}

```

### Correct Thread-Safe Counter

For atomic compound operations, use `java.util.concurrent.atomic` classes:

```java
import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private final AtomicInteger cnt = new AtomicInteger();

    void inc() {
        cnt.incrementAndGet(); // atomic compare-and-swap, respects JMM visibility
    }
}

```

## Summary

- The **Java Memory Model** defines how threads interact through main memory and working memory, abstracting hardware-specific cache implementations.
- The JMM specifies eight primitive memory operations (`read`, `load`, `use`, `assign`, `store`, `write`, `lock`, `unlock`) and provides guarantees for atomicity, visibility, and ordering.
- The **`volatile` keyword** ensures **visibility** by flushing writes immediately to main memory and loading fresh values on read.
- `volatile` inserts **memory barriers** (store barriers after writes, load barriers before reads) that prevent instruction reordering and establish **happens-before** relationships.
- `volatile` does **not** provide atomicity for compound actions like increment operations; use `AtomicInteger` or `synchronized` for such cases.

## Frequently Asked Questions

### Does volatile guarantee atomicity for all operations?

No. `volatile` only guarantees atomicity for single read or write operations on the variable itself. Compound actions such as `count++` (which involves reading, incrementing, and writing) are not atomic even with `volatile`. For atomic compound operations, use `java.util.concurrent.atomic` classes or `synchronized` blocks.

### What is the difference between volatile and synchronized?

`synchronized` provides both **mutual exclusion** (only one thread executes the block at a time) and **visibility** (flushes memory), whereas `volatile` provides only **visibility** and **ordering** guarantees without mutual exclusion. `volatile` is lighter-weight but cannot protect compound operations or critical sections requiring exclusive access.

### When should I use volatile instead of AtomicInteger?

Use `volatile` for simple flags, state indicators, or safe publication of immutable objects where you only need to ensure that the latest value is visible to all threads. Use `AtomicInteger` when you need to perform compound operations like `incrementAndGet()`, `compareAndSet()`, or any read-modify-write sequence that must execute atomically.

### How do memory barriers work in the JVM?

Memory barriers are low-level CPU instructions (or compiler constraints) that prevent reordering of load and store operations. A **store barrier** (SFENCE) ensures all writes before the barrier complete before any writes after it, while a **load barrier** (LFENCE) ensures reads complete in order. The JVM inserts these automatically around `volatile` accesses to enforce the JMM ordering guarantees without requiring explicit programmer intervention.