Discussing volatile and the Java Memory Model (JMM)

Before discussing volatile we need to understand JMM (Java Memory Model) first. Jumping straight to volatile without JMM feels odd, so let's cover JMM. To guarantee correctness of shared memory (visibility, ordering, atomicity), the memory model defines the rules for read/write behavior in multithreaded programs on shared memory systems.

Following up on my two previous posts, What on Earth Is the AQS Everyone Keeps Talking About? and What Is CAS (Compare and Swap) in Java? — both mentioned volatile. Though the experts online have beaten this topic to death, I’ll review it my own way and add my own understanding. I’m self-taught in Java, so this isn’t the standard textbook answer; there’s my own take mixed in. Corrections welcome.

JMM (Java Memory Model)

Before discussing volatile we need to understand JMM (Java Memory Model) first. Jumping straight to volatile without JMM feels odd, so let’s cover JMM. To guarantee correctness of shared memory (visibility, ordering, atomicity), the memory model defines the rules for read/write behavior in multithreaded programs on shared memory systems.

The JVM defines its own memory model too. Careful — the JMM here and the JVM memory model are not the same thing, and you shouldn’t compare them; they’re completely different. Don’t get them mixed up.

Simply put, Java splits memory into main memory and working memory. Main memory is what everyone shares; working memory belongs to each individual thread, like a CPU cache — each core has its own cache rather than sharing RAM. When a program operates on a variable it operates on its own working memory, then writes back to main memory. So problems arise when multiple threads touch the same variable. That’s why JMM needs to guarantee atomicity, visibility, and ordering.

The Java memory model differs from the hardware memory architecture. Hardware doesn’t distinguish thread stacks from heaps; physically, both live in main memory, though parts of the thread stack and heap may sit in CPU caches or CPU registers. I saw someone online claim working memory lives on the stack, and I partly disagree — I think working memory is an abstraction of the CPU cache. Not sure whether that’s right.

Atomicity

JMM guarantees that reads and writes of primitive types are atomic, except for long and double. Of course synchronized also guarantees atomicity, relying on the monitorenter and monitorexit monitor instructions.

Why are long and double special? Because both are 64-bit, and to accommodate 32-bit CPUs they get split into two 32-bit operations, so the JVM spec doesn’t guarantee atomicity there — though it encourages JVMs to implement it. I recall adding volatile makes it atomic, but I’m not certain; you’d have to check the specific JVM implementation’s docs. Too detailed for here.

Visibility

That’s the volatile we’re about to discuss: it forces variable assignment to be flushed back to main memory and forces reads to load from main memory, ensuring every thread always sees the latest value. That’s visibility.

Ordering

Also volatile: it blocks instruction reordering, plus the happens-before rule. We’ll unpack those slowly later. This section is about JMM, so let’s stop here.

Volatile

Finally the main topic. This thing is written all over the JUC package, so it must matter. Let’s see what it does.

Volatile Visibility

From the JMM intro above we know programs don’t interact with main memory directly; they copy a snapshot into their own working memory and operate there. So thread A operating on core 1 and thread B on core 2 may simply not see each other. Marking a variable volatile solves this — once modified, other threads immediately see the new value. How?

First, understand cache coherence protocols, such as Intel’s MESI.

MESI (Cache Coherence Protocol)

When a CPU modifies a variable and finds that other cores also cache it, it notifies those cores to mark their cache invalid, so they read from main memory next time rather than from their own cache.

If volatile is this good, why not slap it on every variable? Abusing it causes problems. As described above, cache coherence relies on CPU-to-CPU communication over the bus, and bus bandwidth is limited. If you use large amounts of volatile together with CAS spinning, you create a message storm on the bus that eats bandwidth, and machine performance drops.

Volatile Ordering

volatile prohibits instruction reordering. Modern systems reorder our instructions for efficiency, both during compilation and during CPU execution — if the previous instruction has no dependency on the next, they may be reordered.

volatile inserts memory barriers at appropriate positions during compilation. There are four kinds:

  • StoreStore: inserted between two Stores, ensuring later Store operations can see the earlier Store

  • LoadLoad: inserted between two Loads, ensuring the later Load reads data the earlier Load read

  • LoadStore: inserted between a Load and a Store, ensuring the Store can read what the earlier Load read before executing

  • StoreLoad: inserted between a Store and a Load, ensuring the Load reads what the earlier Store wrote before executing

That’s a bit much. Starting with JDK 5, the happens-before concept was introduced to describe memory visibility between operations. With it we can say: a write to a volatile field happens-before any subsequent read of that volatile field by any thread. In plain language: once you change a volatile field, any thread reading it later will see it.

Volatile Cannot Guarantee Atomicity

Visibility above looks powerful, but operating on a volatile variable directly still can’t guarantee atomicity. You can see its changes, and memory barriers secure happens-before so single reads/writes are ordered — but that only guarantees you read the latest value. After you read it, while you’re working, other threads may still be modifying it. So atomicity isn’t guaranteed.

volatile vs. synchronized

Many people call volatile a lightweight synchronized. volatile guarantees visible reads only; when writes are involved, you can’t stop other threads from modifying too.