iT邦幫忙

2026 iThome 鐵人賽

0
Build on Google AI

把Google AI當夥伴打造個人高效Coding工作流系列 第 18 篇

第十八天Java記憶體模型、volatile可見性與指令重排、CAS與Atomic原子類

  • 分享至 

  • xImage
  •  

一、 併發程式設計的三大核心特性
任何多執行緒安全問題,本質上都是違反了以下三者之一:

  • 原子性(Atomicity):一個或多個操作要嘛全部執行完畢且中途不可被打斷,要嘛完全不執行。

  • 可見性(Visibility):當一個執行緒修改了共享變數的值,其他執行緒能夠立即得知最新修改。

  • 有序性(Ordering):程式執行的順序依照代碼的先後順序執行(編譯器與 CPU 在不改變單執行緒語義的前提下,可能對指令進行重排序最佳化)。


二、 Java 記憶體模型(JMM, Java Memory Model)抽象結構
現代多核心CPU都有各自獨立的高速快取(L1/L2/L3 Cache),JMM定義了一套規範來遮蔽不同硬體快取架構的差異:

  • 主記憶體:所有執行緒共享的區域,存放所有的實例欄位、靜態變數。
  • 本地工作記憶體:每個執行緒私有的虛擬概念,涵蓋CPU暫存器、寫入緩衝區與快取。
  • 資料不一致根源:執行緒不能直接在主記憶體修改變數,必須先複製到本地工作記憶體,修改完成後再非同步刷新回主記憶體。若刷新延遲,其他執行緒就會讀到過期舊資料。

三、 volatile 的底層機制與極限volatile是JVM提供的最輕量級同步機制,具備以下兩大保證與一個致命限制:

  1. 保證可見性寫入時:JVM會在底層生成帶有lock前綴的組合語言指令,強制將寫入工作記憶體的數值立即同步刷新回主記憶體。讀取時:透過CPU的快取一致性協定,強制使其他CPU核心中該變數的快取行無效,迫使其他執行緒重新從主記憶體抓取最新值。
  2. 保證有序性:禁止指令重排序
  • 記憶體屏障(Memory Barrier / Fence):
    編譯器在生成字節碼時,會在volatile變數的讀寫前後插入特定屏障指令:
    寫操作前後插入 StoreStore與StoreLoad屏障,防止普通寫與volatile寫重排。
    讀操作之後插入LoadLoad與LoadStore屏障,防止volatile讀與後續普通讀寫重排。
  • 典型應用:Day07提到的雙重檢查鎖單例模式,必須靠volatile阻止物件初始化過程中的指令重排序。
  1. 致命限制:不保證原子性volatile完全無法保證非原子操作的安全性!最典型的就是計數器累加count++。
    count++在字節碼層面包含三個獨立步驟:
  • getfield(讀取變數到操作數棧)。
  • iadd(數值加 1)。
  • putfield(寫回工作記憶體)。
    即使 count 宣告為 volatile,若多個執行緒同時讀到相同的初始值並各自執行加 1,最終寫回時依然會發生寫覆蓋,導致資料遺失。

四、 CAS 機制(Compare-And-Swap)與無鎖併發為了解決原子性問題,傳統做法是使用 synchronized 或 ReentrantLock 加鎖,但鎖機制涉及作業系統線程掛起與上下文切換,開銷昂貴。JUC 引入了基於硬體 CPU 指令的樂觀無鎖機制:CAS。

  1. CAS運作哲學
    CAS依賴CPU原生指令cmpxchg,包含三個操作數:
  • $V$(Memory Value):要修改的記憶體真實變數位址。
  • $A$(Expected Value):預期變數當前的值(舊值)。
  • $B$(New Value):準備寫入的新值。
    執行邏輯:當且僅當 $V$ 記憶體位址上的真實值等於預期值 $A$ 時,才將 $V$ 改為新值 $B$;若不相等(代表這期間有其他執行緒偷改了資料),則放棄修改或進行自旋重試(Spin)。整個比對並替換的過程在 CPU 層面是單一原子操作。
  1. CAS 的三大經典缺陷
  • ABA 問題:變數原本是 A,被執行緒 1 改成 B,又被改回 A。此時執行緒 2 進行 CAS 時比對依然是 A,誤以為沒被動過。
    解決解法:增加版本號機制,改用 JUC 提供的 AtomicStampedReference(每次變更連同版本號一併更新,如 (A, 1) -> (B, 2) -> (A, 3))。
  • 自旋開銷過大(Spin Loop):若在高併發衝突下,多個執行緒長時間 CAS 失敗並持續 while(true) 重試,會霸佔 CPU 資源,導致 CPU 使用率居高不下。
  • 只能保證單一變數的原子操作:CAS 只能針對單一共享變數;若需原子性保護多個變數或一段複雜邏輯,依然必須回歸到鎖機制(Lock)。

五、 JUC Atomic 原子類別體系
java.util.concurrent.atomic 套件底層完全基於 volatile + CAS + Unsafe 類別 實作,常見類別劃分:

  1. 基本型別原子類:
    AtomicInteger、AtomicLong、AtomicBoolean。
    常用方法:incrementAndGet()、compareAndSet(expect, update)。

  2. 參考型別原子類:
    AtomicReference:將整個自訂物件封裝為原子引用。
    AtomicStampedReference:帶有整數版本號戳記,徹底解決 ABA 問題。

  3. 超高併發計數特化器:LongAdder(Java 8+ 推薦):
    在極高併發情境下,大量執行緒同時 CAS 更新同一個 AtomicLong 會引發激烈的自旋消耗。
    分段累加思想(Cell Array):LongAdder 將單一變數分散為一個陣列(Cell[]),不同執行緒分散更新不同的 Cell;取總和時呼叫 sum() 將所有 Cell 與 Base 累加合併。在寫入密集的計數場景下,吞吐量遠超 AtomicLong。


上一篇
第十七天Thread生命週期六大狀態切換、synchronized底層Monitor機制與死鎖預防
系列文
把Google AI當夥伴打造個人高效Coding工作流 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言