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

Java 記憶體模型深度解析:順序一致性與 JMM 保證機制

     編輯:Java 綜合教學

資料競爭與順序一致性保證

當多線程程序未正確同步時,會產生資料競爭。JMM 規範定義資料競爭需同時滿足三個條件:

  1. 一個線程寫入某變數
  2. 另一個線程讀取同一變數
  3. 寫入與讀取未通過同步機制排序

存在資料競爭的程序執行結果往往違反直覺。JMM 承諾:若程序正確同步(廣義同步,含 lock、volatile、final),其執行將具備順序一致性——即執行結果與順序一致性記憶體模型中的結果相同。

順序一致性記憶體模型(SC)

SC 是電腦科學家理想化的理論參考模型,提供極強的記憶體可見性保證,具備兩大核心特性:

  • 程序順序執行:單線程內所有操作必須按程序順序執行
  • 全局單一操作序列:所有線程只能看到一個一致的全局操作執行順序;每個操作原子執行且立即對所有線程可見

順序一致性模型示意圖

概念上,SC 擁有單一全局記憶體,透過「開關」串行化所有線程的讀寫操作。任意時刻僅有一線程連接記憶體,確保操作原子性與全局可見性。

同步與非同步程序在 SC 模型中的表現

同步程序(正確加鎖)

線程 A 執行完釋放鎖 → 線程 B 獲取同一鎖。SC 模型中操作完全按程序順序串行執行:

同步程序執行時序

非同步程序

整體執行順序無序,但所有線程看到的操作順序一致。例如線程 A(B1→A1→A2→B2→A3→B3) 與線程 B 看到的順序完全相同,因為每個操作立即對所有線程可見。

非同步程序執行時序

JMM 與 SC 的關鍵差異

在 JMM 中,非同步程序不僅整體無序,不同線程看到的操作順序可能不一致。原因在於:線程將寫入緩存在本地記憶體,未刷新到主記憶體前,該寫入僅對當前線程可見;其他線程會認為寫入根本未發生。只有刷新到主記憶體後,其他線程才能看見。

同步程序在 JMM 中的順序一致性效果

以 SynchronizedExample 為例:

class SynchronizedExample {
    int a = 0;
    boolean flag = false;

    public synchronized void writer() {
        a = 1;
        flag = true;
    }

    public synchronized void reader() {
        if (flag) {
            int i = a;
            // ...
        }
    }
}

假設線程 A 執行 writer(),線程 B 執行 reader()。JMM 保證執行結果與 SC 模型一致。關鍵在於:

  • JMM 允許臨界區內代碼重排序
  • 但禁止代碼「逸出」臨界區(破壞監視器語義)
  • JMM 在退出監視器與進入監視器兩個關鍵時間點做特殊處理,使線程擁有與 SC 相同的記憶體視圖
  • 監視器互斥特性導致線程 B 無法「觀察」到線程 A 臨界區內的重排序

兩模型執行時序對比

這體現了 JMM 核心設計哲學:在不改變正確同步程序執行結果的前提下,盡可能為編譯器與處理器優化留出空間。

非同步程序的執行特性與最小安全性

JMM 對非同步程序僅提供最小安全性:線程讀取到的值,要麼是先前某線程寫入的值,要麼是默認值(0、null、false),絕不會憑空產生值。為實現此保證,JVM 在堆上分配對象前會先清零記憶體空間。

非同步程序在 JMM 與 SC 的三大差異

  1. SC 保證所有線程看到一致操作順序;JMM 不保證
  2. SC 保證操作原子性(單總線事務);JMM 中 64 位 long/double 寫入可能被拆分為兩個 32 位操作(JSR-133 前讀寫皆可拆分,JSR-133 後僅允許寫入拆分)
  3. 差異三與處理器總線機制密切相關

總線機制與 64 位變量原子性問題

現代處理器透過總線事務(讀事務/寫事務)在處理器與記憶體間傳輸數據。總線仲裁確保同一時刻僅一處理器訪問記憶體,保證單個總線事務內的讀寫原子性。

總線工作機制

32 位處理器上 64 位變量寫入非原子性風險

為照顧 32 位處理器,Java 規範鼓勵但不強制 JVM 保證 64 位 long/double 寫入原子性。若 JVM 選擇拆分寫入,兩個 32 位寫操作可能分配到不同總線事務,導致寫入非原子。

64位寫入拆分導致無效值

如圖:處理器 A 寫 long 變量被拆分為兩個 32 位寫事務,處理器 B 讀取分配到單個讀事務。若時序交錯,B 將讀到「僅寫了一半」的無效值。

JSR-133 記憶體模型的改進

自 JDK 5 (JSR-133) 起:

  • 僅允許 64 位 long/double 寫入 拆分為兩個 32 位寫操作
  • 任意讀操作 必須具備原子性(單個讀事務執行)

這顯著降低了非原子讀寫導致的數據破壞風險。

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