Understanding Java volatile: Usage, Memory Model, Visibility, Atomicity, and Instruction Reordering
This article provides a comprehensive guide to Java's volatile keyword, covering its pronunciation, purpose, three core properties, interaction with the Java Memory Model, visibility and atomicity examples, instruction reordering, memory barriers, and practical usage such as double‑checked locking and when to prefer volatile over heavier synchronization mechanisms.
The article begins by showing how to pronounce volatile in English and Chinese, then asks what the keyword does in Java.
1. What volatile does in Java
Provides a lightweight synchronization mechanism with three key characteristics:
Understanding these properties requires knowledge of the Java Memory Model (JMM), which the article explains with diagrams and a concise definition.
2. Java Memory Model
The JMM defines how variables are stored in main memory and accessed via each thread's working memory . The article describes the flow of reads and writes, showing why a non‑volatile variable update may not be visible to other threads.
class ShareData {
int number = 0;
public void setNumberTo100() {
this.number = 100;
}
}A demonstration program creates a child thread that sleeps for three seconds, then sets number to 100. Without volatile , the main thread never sees the change; with volatile , it does.
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();
System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number);
}, "子线程").start();
while (myData.number == 0) { }
System.out.println(Thread.currentThread().getName() + "\t 主线程感知到了 number 不等于 0");
}
}3. Volatile does not guarantee atomicity
The article shows a test where 20 threads each increment a shared volatile int number 1000 times. Results vary (e.g., 19144, 20000, 19378) because number++ consists of three byte‑code instructions, allowing race conditions.
public static volatile int number = 0;
public static void increase() { number++; }To achieve atomicity, the article recommends using synchronized blocks or the JUC AtomicInteger class.
public synchronized static void increase() { number++; }
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();4. Instruction reordering and memory barriers
Volatile also prevents the compiler and CPU from reordering instructions by inserting memory barriers (StoreStore, StoreLoad, LoadLoad, LoadStore). The article explains how these barriers work for both reads and writes, illustrated with diagrams.
5. Practical use cases
Typical scenarios include status flags for loop termination and the double‑checked locking singleton pattern. The singleton example shows why the instance field must be declared volatile to avoid seeing a partially constructed object.
private static volatile VolatileSingleton instance = null;
public static VolatileSingleton getInstance() {
if (instance == null) {
synchronized (VolatileSingleton.class) {
if (instance == null) {
instance = new VolatileSingleton();
}
}
}
return instance;
}Finally, the article summarizes the key take‑aways: volatile guarantees visibility and ordering, does not guarantee atomicity (except for 64‑bit long/double reads/writes), and should be used when updates are independent of the current value and locking would be too heavyweight.
Wukong Talks Architecture
Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.
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.