常聞「Java 原子操作線程安全」。依定義原子操作不可中斷,故被視為線程安全。實際上,部分原子操作不保證線程安全。
為減少 synchronized 關鍵字使用(同步有效能損耗,雖現代 JVM 持續優化)。程式設計師追求效率,此議題延續至今。
32 位元或以下賦值為原子。32 位平台上,除 double、long(64 位)外,其他基本型別與物件參考(機器指標,通常 32 位)操作均為原子。
小迷思:基本型別大小由語言保證?錯誤。Java 保證的是數值範圍而非 JVM 儲存大小。int 範圍固定,但某 JVM 可能用 32 位實作,另一個用 64 位。重申:所有平台保證的是數值範圍,32 位及以下值操作為原子。
核心問題:JVM 允許線程在私有工作記憶體保存變數副本,為提升效能不強制每次讀主記憶體。雖賦值原子,但讀寫若針對私有副本而非主記憶體,線程安全不被保證。JVM 此行為亦非保證。
class RealTimeClock {
private int clkID;
public int clockID() { return clkID; }
public void setClockID(int id) { clkID = id; }
}
兩線程並發:T1 setClockID(5) 寫入私有記憶體;T2 setClockID(10) 寫入私有記憶體;T1 clockID() 從私有記憶體讀回 5。正確應回傳 10(T2 寫入主記憶體),實際回傳 5,發生可見性問題。
私有副本與主記憶體一致需滿足兩條件之一:
volatile:每次存取強制與主記憶體同步,語言保證原子性(含 64 位值)。注意:多數 JVM 未正確實作 volatile。synchronized 方法/區塊中存取:進入獲取鎖、退出釋放鎖時同步主記憶體。volatile:每次存取同步,更新頻繁時可能比同步更慢synchronized:僅鎖獲取/釋放時同步,但降低並發性大量更新且不想每次同步、或需排除並發性時考慮同步。