Mastering Java volatile: Visibility, Atomicity, and Memory Model Explained
This article provides a comprehensive guide to Java's volatile keyword, covering its pronunciation, core features such as visibility, lack of atomicity, and instruction reordering prevention, the Java Memory Model, cache‑coherence mechanisms, practical code examples, and best‑practice scenarios like double‑checked locking and when to prefer volatile over heavier synchronization constructs.
1. How to pronounce volatile?
Pronunciation: ˈvɒlətaɪl (British) / ˈvɑːlətl (American).
2. What does volatile do in Java?
Provides a lightweight synchronization mechanism with three main characteristics: Ensures visibility across threads. Does not guarantee atomicity. Prevents instruction reordering.
3. What is the Java Memory Model (JMM)?
The JMM defines how variables are stored and accessed in main memory and each thread's working memory.
3.1 Why do we need a memory model?
It abstracts hardware and OS memory access differences, providing a consistent view for Java programs.
3.2 Core specifications
Define access rules for program variables.
Detail how variable values are stored in memory.
Detail how values are retrieved from memory.
3.3 Two memory areas
Main memory – shared heap and physical RAM.
Working memory – per‑thread stack, registers, and caches.
3.4 JMM rules
All variables reside in main memory.
Main memory is part of the JVM.
Each thread has its own working memory.
Thread operations work on copies in working memory.
Variable updates must be written back to main memory.
Threads cannot directly access another thread's working memory.
All inter‑thread communication occurs via main memory.
4. Example: Using volatile
Scenario: a shared field number is updated by a child thread. Without volatile, the main thread may never see the change.
<code>class ShareData {
int number = 0;
public void setNumberTo100() { this.number = 100; }
}
</code>Running two threads without volatile shows the main thread never exits the loop.
<code>public class volatileVisibility {
public static void main(String[] args) {
ShareData myData = new ShareData();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {}
myData.setNumberTo100();
System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number);
}, "ChildThread").start();
while (myData.number == 0) { }
System.out.println(Thread.currentThread().getName() + "\t Main thread sees number != 0");
}
}
</code>Adding volatile to number makes the update visible:
<code>class ShareData {
volatile int number = 0;
public void setNumberTo100() { this.number = 100; }
}
</code>5. Why can other threads see the update?
Modern CPUs use a snooping protocol and the MESI cache‑coherence protocol to keep caches consistent.
5.1 Snooping
Each CPU monitors the bus; when a cache line is modified, other CPUs invalidate their copies.
5.2 MESI states
Write operations insert StoreStore and StoreLoad barriers; read operations insert LoadLoad and LoadStore barriers, ensuring proper ordering.
6. Volatile does not guarantee atomicity
Incrementing a volatile variable ( number++ ) still consists of three bytecode instructions (getstatic, iadd, putstatic), allowing race conditions.
<code>public static volatile int number = 0;
public static void increase() { number++; }
</code>Running 20 threads each incrementing 1000 times yields nondeterministic results (e.g., 19144, 20000, 19378).
7. Ensuring correct results
7.1 Synchronized block
<code>public synchronized static void increase() { number++; }
</code>7.2 AtomicInteger
<code>public static AtomicInteger atomicInteger = new AtomicInteger();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
atomicInteger.getAndIncrement();
}
}).start();
}
</code>AtomicInteger consistently produces the expected total (20000).
8. Instruction reordering and how volatile prevents it
Compilers and CPUs may reorder independent instructions for performance. Volatile inserts memory barriers that block such reordering, preserving program order in single‑threaded execution.
8.1 Types of reordering
Compiler optimization.
CPU instruction‑level parallelism.
Memory system reordering due to caches and buffers.
8.2 Example of reordering affecting visibility
Thread 1 writes num = 1; flag = true; . Without volatile, the writes may appear as flag = true; num = 1; to Thread 2, causing it to read an outdated num .
8.3 How volatile adds barriers
For a volatile write, a StoreStore barrier precedes the write and a StoreLoad barrier follows it. For a volatile read, LoadLoad and LoadStore barriers are inserted after the read.
9. Common volatile applications
Double‑checked locking for lazy‑initialized singletons:
<code>class VolatileSingleton {
private static volatile VolatileSingleton instance = null;
private VolatileSingleton() {}
public static VolatileSingleton getInstance() {
if (instance == null) {
synchronized (VolatileSingleton.class) {
if (instance == null) {
instance = new VolatileSingleton();
}
}
}
return instance;
}
}
</code>10. When to use volatile
Use volatile when:
Writes do not depend on the current value or only a single thread updates the variable.
The variable is not part of a larger invariant.
No lock is required for reads.
Typical use cases include status flags for loop termination and lightweight inter‑thread signaling.
11. Differences between volatile and synchronized
volatile can only modify fields; synchronized can protect methods or blocks.
volatile does not guarantee atomicity; synchronized does.
volatile is non‑blocking; synchronized may block.
volatile is a lightweight lock; synchronized is heavier.
Both ensure visibility and ordering.
12. Summary
volatile guarantees visibility across threads.
volatile prevents instruction reordering in single‑threaded contexts.
volatile does not guarantee atomicity; use synchronized or atomic classes for compound actions.
64‑bit long/double reads/writes are atomic when declared volatile.
volatile enables efficient double‑checked locking.
volatile is ideal for simple state‑flag checks.
References: "深入理解Java虚拟机", "Java并发编程的艺术", "Java并发编程实战".
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.