In a single-threaded Java program, you are protected by a beautiful lie called as-if-serial semantics. If you write int x = 1; int y = 2;, the JVM and CPU can reorder those lines however they want to improve performance, but they promise that the result will be exactly as if they ran in order. Inside that single thread, the reordering is invisible.
As soon as you introduce a second thread, the lie falls apart. That second thread doesn't see the "as-if-serial" promise; it sees the raw memory as it updates. Code that looks perfectly logical can suddenly fail in ways that seem impossible. This is where the Java Memory Model (JMM) comes in—it is the official "contract" that defines exactly when and how threads are allowed to see each other's changes.
1. The Core Problem: Performance over Predictability
Most developers assume the JVM executes code exactly line-by-line as written. In reality, the JVM and your CPU are obsessed with speed. To run faster, they perform optimizations that create two main issues: Reordering and Visibility.
Reordering: The "Out of Order" Execution
The compiler or the CPU might decide to swap two instructions if it thinks the final result will be the same.
// What you wrote:
int a = 1;
boolean flag = true;
// What the CPU might actually execute:
boolean flag = true;
int a = 1;
For a single thread, this swap doesn't matter. But if another thread is waiting for flag to be true so it can read a, it might see flag == true before a has actually been set to 1.
Visibility: The Cache Problem
Modern CPUs have their own local caches (L1, L2, L3). When a thread updates a variable, it might only save that change in its local CPU cache to save time. Other threads, running on different CPU cores, will continue to read the old value from their own caches or main memory. The change is "invisible" to them.
2. The Solution: "Happens-Before" (HB)
The JMM doesn't promise that everything will always be in order. Instead, it provides a set of rules called the Happens-Before relationship.
Think of Happens-Before as a "visibility bridge." If Action A happens-before Action B, then any change made by Action A is guaranteed to be visible to the thread performing Action B.
The Most Important Rules:
- Program Order: In a single thread, every action happens-before any action that comes later in the code.
- Volatile Variable Rule: A write to a
volatilefield happens-before every subsequent read of that same field. (This is the "signal" we use to bridge threads). - Monitor Lock Rule: Releasing a lock (
synchronized) happens-before any subsequent acquisition of that same lock. - Thread Life Cycle: Calling
thread.start()happens-before any action in that thread. All actions in a thread happen-before a successfulthread.join()on that thread.
3. The volatile Modifier: A Modern Guide
A common mistake is thinking volatile is just for "disabling caches." It's more powerful than that.
When you write to a volatile variable, the JVM ensures two things:
- Visibility: The write is immediately flushed to main memory, and any subsequent read will pull the latest value.
- Ordering (The Barrier): The JVM prevents instructions from being reordered around the volatile read/write. It acts as a "memory barrier."
What volatile does NOT do: Atomicity
This is the biggest landmine in Java. volatile does not make compound operations atomic.
public volatile int count = 0;
public void increment() {
count++; // NOT THREAD-SAFE
}
count++ is actually three steps: read, add 1, write. If two threads do this at the same time, they might both read the same value, add 1, and write the same result back, losing one increment. For this, you need AtomicInteger or synchronized.
4. Unsafe Publication: Why "null" isn't always null
One of the strangest bugs in Java is when a thread sees an object that is "half-initialized."
// Thread A
shared = new Helper(42);
// Thread B
if (shared != null) {
System.out.println(shared.x); // Could print 0 instead of 42!
}
Because of reordering, the CPU might assign the memory address of the new Helper object to the shared variable before the constructor has finished setting x = 42.
How to fix this (Safe Publication):
- Make the
sharedfieldvolatile. - Initialize it inside a
synchronizedblock. - Make the fields inside the object
final. The JMM gives special visibility guarantees tofinalfields once the constructor finishes.
5. Double-Checked Locking (DCL)
The classic way to create a lazy singleton safely is the Double-Checked Locking pattern. It relies heavily on volatile.
private volatile Resource resource;
public Resource getResource() {
Resource result = resource;
if (result == null) { // First check (no locking)
synchronized (this) {
result = resource;
if (result == null) { // Second check (with locking)
resource = result = new Resource();
}
}
}
return result;
}
Note: We use the local variable result to reduce the number of times we have to read the volatile field, which is a small performance optimization.
6. Beyond Volatile: VarHandles (Java 9+)
In modern Java, if you need even more control than volatile provides, you can use the VarHandle API. It allows you to choose exactly how much "strictness" you want:
- Opaque: Ensures the value isn't cached, but allows reordering.
- Acquire/Release: A lighter version of volatile that only enforces ordering in one direction.
- Volatile: The full-strength version we discussed.
Practical Checklist for Concurrent Code
- Is the variable shared? If yes, it must be protected by
volatile,Atomicclasses, or a lock. - Are you doing more than a simple write? If you are reading-then-writing (like
count++),volatileis not enough. UseAtomicInteger. - Is your object fully built? Never let the
thisreference "escape" from a constructor (e.g., by passing it to another thread) before the constructor is finished. - Can you use
final? Always preferfinalfields. They are the simplest way to ensure thread-safety for data that doesn't change.
Citations & Further Reading
- Java Language Specification, Chapter 17: Threads and Locks
- JSR-133 (Java Memory Model) FAQ
- Aleksey Shipilёv: JMM Pragmatics
- VarHandle API Documentation
Originally published at https://rex.mindmeld360.com.
Top comments (0)