當共享變量(成員變量、靜態變量)被 volatile 修飾後,具備兩層關鍵語義:
// 線程 1
boolean stop = false;
while (!stop) {
doSomething();
}
// 線程 2
stop = true; // 嘗試停止線程 1
問題:線程 1 可能將 stop 複製到工作記憶體(CPU 快取),線程 2 修改主記憶體後,線程 1 看不到變化,導致無限循環。
volatile 變量時,強制將修改值立即刷新到主記憶體對 volatile 變量的單次讀/寫,等同於使用同一把監視器鎖同步:
// volatile 版本
class VolatileExample {
volatile long vl = 0L;
public void set(long l) { vl = l; } // 單次寫
public long get() { return vl; } // 單次讀
public void increment() { vl++; } // 復合操作:讀+改+寫 (非原子!)
}
// 等價的鎖版本
class LockExample {
long vl = 0L;
public synchronized void set(long l) { vl = l; }
public synchronized long get() { return vl; }
public void increment() {
long temp = get(); // 已同步的讀
temp += 1L; // 普通寫
set(temp); // 已同步的寫
}
}
關鍵區別:volatile 僅保證單次讀/寫原子性,不保證復合操作 (如 vl++) 的原子性。
class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2 (volatile 寫)
}
public void reader() {
if (flag) { // 3 (volatile 讀)
int i = a; // 4
}
}
}
happens-before 鏈路:
結果:線程 A 寫 volatile 前的所有共享變量修改,對線程 B 讀同一 volatile 後可見。
| 操作 | 內存語義 |
|---|---|
| volatile 寫 | 將當前線程本地記憶體中共享變量刷新到主記憶體 |
| volatile 讀 | 將當前線程本地記憶體設為無效,強制從主記憶體讀取 |
通俗理解:寫是發送消息,讀是接收消息,主記憶體是郵箱。
public class Counter {
public volatile int count = 0;
public void increment() { count++; } // 非原子!
public static void main(String[] args) throws InterruptedException {
Counter c = new Counter();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) c.increment();
});
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println(c.count); // 輸出 < 10000,非 10000
}
}
原因:count++ 包含讀取、加一、寫入三步,可能被線程交錯執行。
public synchronized void increment() { count++; }
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try { count++; } finally { lock.unlock(); }
}
private final AtomicInteger count = new AtomicInteger(0);
public void increment() { count.getAndIncrement(); }
// x、y 為普通變量,flag 為 volatile
x = 2; // 語句 1
y = 0; // 語句 2
flag = true; // 語句 3 (volatile 寫)
x = 4; // 語句 4
y = -1; // 語句 5
規則:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查 (無鎖)
synchronized (Singleton.class) { // 加鎖
if (instance == null) { // 第二次檢查
instance = new Singleton(); // 初始化
}
}
}
return instance;
}
}
為什麼需要 volatile? 防止 instance = new Singleton() 指令重排序導致其他線程看到半初始化對象。
必須同時滿足:
true/false、狀態標記)典型用途:狀態標記、雙重檢查鎖、簡單的發布-訂閱模式。不可替代:計數器、複雜邏輯、需要原子性的復合操作。