許多開發者認為:「Java 中 32 位元以下賦值是原子的,所以 int、boolean、引用賦值是執行緒安全的。」這是嚴重誤區。 原子性只保證單一操作不可分割,不保證可見性與有序性。本文深入剖析為何原子操作可能導致資料競爭,以及正確解決方案。
每條執行緒擁有私有工作記憶體(快取、寄存器),讀寫共享變數時:
JMM 允許執行緒延遲寫回、重排序指令,導致可見性問題:執行緒 A 寫入的值,執行緒 B 可能永遠看不見。
class RealTimeClock {
private int clkID; // 32位,賦值原子
public int clockID() { return clkID; }
public void setClockID(int id) { clkID = id; }
}
場景:執行緒 T1 呼叫 setClockID(5),T2 呼叫 setClockID(10),T1 再呼叫 clockID()。
可能發生的悲劇:
clkID,得到 5(舊值),而非主記憶體的 10。賦值是原子的,但讀取看見的是過期快取。這就是「原子 ≠ 執行緒安全」的鐵證。
volatile 不保證複合操作原子性(如 volatile int i; i++; 仍非原子)。class SafeClock {
private volatile int clkID; // 保證可見性
public int clockID() { return clkID; }
public void setClockID(int id) { clkID = id; }
}
class SyncClock {
private int clkID;
public synchronized int clockID() { return clkID; }
public synchronized void setClockID(int id) { clkID = id; }
}
| 維度 | volatile | synchronized / Lock |
|---|---|---|
| 可見性 | ✅ 每次讀寫主記憶體 | ✅ 加/解鎖時同步 |
| 原子性 | ❌ 單一讀/寫;複合操作需 CAS | ✅ 整個臨界區原子 |
| 互斥性 | ❌ 無 | ✅ 有 |
| 效能 | 極低開銷(無上下文切換) | 較高(鎖競爭、上下文切換) |
| 適用場景 | 單一狀態旗標、獨立變數 | 多變數不變式、複雜業務邏輯 |
Java 5+ java.util.concurrent.atomic 提供無鎖原子操作:
AtomicInteger atomicId = new AtomicInteger(0);
// 原子增量
int newVal = atomicId.incrementAndGet();
// CAS 比較並設定
boolean updated = atomicId.compareAndSet(expected, newValue);
CMPXCHG(Compare-And-Swap)。synchronized(無阻塞、無上下文切換)。AtomicStampedReference 解決。long、double(64 位)在 32 位 JVM 上賦值非原子(分兩次 32 位寫入)。volatile 或在 synchronized 內。原子性 ≠ 執行緒安全。執行緒安全 = 原子性 + 可見性 + 有序性。單一變數用 volatile;多變數/複合操作用 synchronized 或 Lock;高併發計數器用 Atomic*。理解 JMM,才能寫出正確的併發代碼。