首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

破除 Java 迷思:原子操作不等於執行緒安全 — volatile 與 synchronized 的正確姿勢

     編輯:Java 程式設計入門

前言:危險的「原子即安全」假象

許多開發者認為:「Java 中 32 位元以下賦值是原子的,所以 int、boolean、引用賦值是執行緒安全的。」這是嚴重誤區。 原子性只保證單一操作不可分割,不保證可見性與有序性。本文深入剖析為何原子操作可能導致資料競爭,以及正確解決方案。

一、Java 記憶體模型 (JMM) 核心:主記憶體 vs 工作記憶體

每條執行緒擁有私有工作記憶體(快取、寄存器),讀寫共享變數時:

  1. 從主記憶體複製到工作記憶體(read/load)。
  2. 在工作記憶體運算(use/assign)。
  3. 寫回主記憶體(store/write)。

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()。

可能發生的悲劇:

  1. T1 將 5 寫入自己工作記憶體(未刷回主記憶體)。
  2. T2 將 10 寫入自己工作記憶體並刷回主記憶體。
  3. T1 從自己工作記憶體讀取 clkID,得到 5(舊值),而非主記憶體的 10。
賦值是原子的,但讀取看見的是過期快取。這就是「原子 ≠ 執行緒安全」的鐵證。

三、JMM 保證可見性的兩大機制

1. volatile 關鍵字

  • 強制每次 直接從主記憶體讀取、寫入立即刷回主記憶體。
  • 禁止指令重排序(寫屏障 + 讀屏障)。
  • 適用場景:單一變數狀態標記、獨立觀察者模式、雙重檢查鎖單例(Java 5+)。
  • 注意:volatile 不保證複合操作原子性(如 volatile int i; i++; 仍非原子)。
class SafeClock {
    private volatile int clkID; // 保證可見性
    public int clockID() { return clkID; }
    public void setClockID(int id) { clkID = id; }
}

2. synchronized 關鍵字 / Lock

  • 互斥鎖:同一時間只有一個執行緒持有鎖。
  • 可見性保證:解鎖前將工作記憶體共享變數刷回主記憶體;加鎖後從主記憶體重新讀取。
  • 適用場景:多變數組合操作、複雜臨界區、需要互斥的業務邏輯。
class SyncClock {
    private int clkID;
    public synchronized int clockID() { return clkID; }
    public synchronized void setClockID(int id) { clkID = id; }
}

四、volatile vs synchronized 選擇指南

維度volatilesynchronized / Lock
可見性✅ 每次讀寫主記憶體✅ 加/解鎖時同步
原子性❌ 單一讀/寫;複合操作需 CAS✅ 整個臨界區原子
互斥性❌ 無✅ 有
效能極低開銷(無上下文切換)較高(鎖競爭、上下文切換)
適用場景單一狀態旗標、獨立變數多變數不變式、複雜業務邏輯

五、進階:原子類別與 CAS

Java 5+ java.util.concurrent.atomic 提供無鎖原子操作:

AtomicInteger atomicId = new AtomicInteger(0);
// 原子增量
int newVal = atomicId.incrementAndGet();
// CAS 比較並設定
boolean updated = atomicId.compareAndSet(expected, newValue);
  • 底層 CPU 指令 CMPXCHG(Compare-And-Swap)。
  • 高併發下優於 synchronized(無阻塞、無上下文切換)。
  • ABA 問題可用 AtomicStampedReference 解決。

六、長整數與雙精度的特殊規則

  • long、double(64 位)在 32 位 JVM 上賦值非原子(分兩次 32 位寫入)。
  • Java 語言規範不保證 64 位原子性,除非宣告為 volatile 或在 synchronized 內。
  • 64 位 JVM 通常單指令完成,但不可依賴。

結語

原子性 ≠ 執行緒安全。執行緒安全 = 原子性 + 可見性 + 有序性。單一變數用 volatile;多變數/複合操作用 synchronized 或 Lock;高併發計數器用 Atomic*。理解 JMM,才能寫出正確的併發代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved