Mastering Java’s volatile: Visibility, Atomicity, and Memory Barriers Explained
This article provides a comprehensive guide to Java's volatile keyword, covering its pronunciation, role in the Java Memory Model, visibility guarantees, lack of atomicity, instruction reordering prevention, memory barriers, and practical usage patterns such as double‑checked locking and atomic classes.
1. How to pronounce volatile?
British: [ˈvɒlətaɪl] American: [ˈvɑːlətl]
2. What does volatile do in Java?
Lightweight synchronization mechanism provided by the JVM
3. What is the Java Memory Model (JMM)?
The JMM defines how variables are stored in main memory and accessed by threads, describing the rules for reading and writing variables.
3.1 Why do we need JMM?
It hides differences in memory access across hardware and operating systems.
3.2 What does JMM define?
Rules for accessing program variables
Details of storing variable values in memory
Details of retrieving variable values from memory
3.3 The two main memories in JMM
Main memory (heap objects, physical memory)
Working memory (stack area, registers, caches)
3.4 JMM specifications
All variables reside in main memory
Main memory is part of the JVM memory
Each thread has its own working memory
Working memory holds a copy of variables from main memory
Thread operations on variables must occur in working memory
Threads cannot directly access each other's working memory
Variable transfer between threads occurs via main memory
4. Example: Using volatile for visibility
<code>class ShareData { int number = 0; public void setNumberTo100() { this.number = 100; } }</code> <code>public class volatileVisibility { public static void main(String[] args) { ShareData myData = new ShareData(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } myData.setNumberTo100(); }, "子线程").start(); while (myData.number == 0) { } System.out.println("Main thread sees number != 0"); } }</code>Without volatile, the main thread may never see the update.
5. Why can other threads see the update?
Through the cache‑coherency (snooping) protocol and MESI cache‑coherence mechanism, writes invalidate other CPUs' cache lines, forcing them to reload from main memory.
6. Demonstrating that volatile does not guarantee atomicity
<code>public static volatile int number = 0; public static void increase() { number++; }</code>Running 20 threads each incrementing 1000 times may produce results less than 20000 because ++ is not atomic.
<code>public static void increase() { number++; }</code>Bytecode shows three instructions: getstatic, iadd, putstatic, where other threads can interleave between get and put.
7. Ensuring atomic results
7.1 Synchronized block
<code>public synchronized static void increase() { number++; }</code>7.2 AtomicInteger
<code>AtomicInteger atomicInteger = new AtomicInteger(); atomicInteger.getAndIncrement();</code>AtomicInteger consistently yields the expected total.
8. Preventing instruction reordering
Compilers and CPUs may reorder instructions for performance, but volatile inserts memory barriers to forbid such reordering.
8.1 Why reorder?
To improve performance while preserving single‑thread semantics.
8.2 Types of reordering
Compiler optimization reordering
Instruction‑level parallelism reordering
Memory system reordering
8.3 Example of reordering in multithreading
<code>static int num = 0; static boolean flag = false; public static void init() { num = 1; flag = true; } public static void add() { if (flag) { num = num + 5; } }</code>If the write to flag is reordered before the write to num , another thread may see flag true while num is still 0.
8.4 How volatile prevents reordering
Volatile writes insert StoreStore and StoreLoad barriers; volatile reads insert LoadLoad and LoadStore barriers, ensuring proper ordering.
9. Common volatile applications
Double‑checked locking singleton pattern:
<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. Why use volatile despite lacking atomicity?
Volatile provides lightweight visibility guarantees without the blocking overhead of synchronized or lock constructs, making it suitable for status flags and simple checks.
11. Differences between volatile and synchronized
volatile can only modify instance or static fields; synchronized can protect methods or blocks.
volatile does not guarantee atomicity; synchronized does.
volatile does not block threads; synchronized may block.
volatile is a lightweight lock; synchronized is heavyweight.
Both ensure visibility and ordering.
12. Summary
volatile ensures visibility across threads.
volatile prevents instruction reordering in single‑threaded contexts via memory barriers.
volatile does not guarantee atomicity for compound actions like a++ .
64‑bit long and double reads/writes are atomic with volatile.
volatile is useful in double‑checked locking and status‑flag loops.
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.