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

Java 內存模型(JMM)基石:共享內存、抽象結構、重排序規則與 Happens-Before 原則

     編輯:Java 綜合教學

Java 內存模型(JMM)基石:共享內存、抽象結構、重排序規則與 Happens-Before 原則

JMM 是 Java 併發編程的語義基石,定義了線程之間「何時可見、如何有序」。本文系統梳理 JMM 抽象模型、三類重排序、處理器內存屏障、Happens-Before 規則族,為理解 volatile、synchronized、final、並發容器語義打下堅實基礎。

一、 併發模型分類:共享內存 vs 消息傳遞

維度共享內存模型 (Java 採用)消息傳遞模型 (Go/Erlang/Actor)
通信機制隱式:讀/寫公共堆變量顯式:發送/接收消息
同步機制顯式:程序員加鎖/volatile隱式:發送先於接收天然有序
核心挑戰內存可見性、有序性、原子性死鎖、郵箱溢出、消息丟失

Java 線程通信對程序員完全透明,若不理解 JMM 隱式規則,極易遇到「讀不到最新值」「指令重排導致邏輯錯」等詭異 Bug。

二、 JMM 抽象結構:主內存與本地內存


┌─────────────────────────────────────┐
│           主內存 (Main Memory)       │  ← 存儲所有共享變量:堆對象、靜態字段、數組元素
│  ┌─────────┐  ┌─────────┐           │
│  │ 共享變量x│  │ 共享變量y│  ...     │
│  └─────────┘  └─────────┘           │
└──────────────┬──────────────────────┘
               │ 讀/寫
       ┌───────┴───────┐
       ▼               ▼
┌─────────────┐  ┌─────────────┐
│ 線程 A       │  │ 線程 B       │
│ 本地內存     │  │ 本地內存     │
│ (抽象概念)   │  │ (抽象概念)   │
│  x=1 (副本)  │  │  x=0 (副本)  │
└─────────────┘  └─────────────┘
  • 本地內存 不物理存在,涵蓋 CPU 緩存(L1/L2/L3)、寫緩衝區、寄存器、編譯器優化(寄存器分配、公共子表達式消除)等
  • 線程 A 寫入 x=1 → 先存本地內存 → 後刷新到主內存 → 線程 B 從主內存讀取 → 更新本地副本
  • JMM 控制「主內存 ↔ 本地內存」交互協議,提供可見性保證

三、 重排序:三類來源與 JMM 管控策略

數據依賴性:同一線程內,若兩操作訪問同一變量且至少一寫,存在數據依賴。編譯器/CPU 不會 重排序有數據依賴的操作(as-if-serial 語義保證單線程結果不變)。但跨線程的數據依賴不被硬件自動保護,需靠同步手段。

四、 處理器重排序與內存屏障指令

主流 CPU 允許的重排序矩陣:

類型發生階段例子JMM 對策
編譯器重排序源碼 → 字節碼/機器碼獨立語句調換順序、循環不變量外提禁止特定類型(如 volatile 寫後不得前移)
指令級並行重排序CPU 流水線動態調度Load/Store 亂序執行、分支預測要求編譯器插入內存屏障指令
內存系統重排序緩存一致性協議 (MESI)寫緩衝區導致 Store-Load 重排屏障指令強制刷新/失效緩存行

JMM 要求編譯器在生成代碼時插入四類屏障:

CPULoad-LoadLoad-StoreStore-StoreStore-Load數據依賴
x86/x64 (TSO)NNNYN
ARM/POWERYYYYN
IA-64YYYYN
屏障類型指令示例語義對應 CPU 指令
LoadLoadLoad1; LoadLoad; Load2確保 Load1 裝載完成才執行 Load2lfence / ld.acq
StoreStoreStore1; StoreStore; Store2確保 Store1 刷新到內存對其他 CPU 可見才執行 Store2sfence / st.rel
LoadStoreLoad1; LoadStore; Store2確保 Load1 裝載完成才執行 Store2 刷新mfence / sync
StoreLoadStore1; StoreLoad; Load2全能屏障:Store1 可見前,禁止 Load2 裝載mfence / dmb sy / lock addl

StoreLoad 開銷最大(需刷寫緩衝區),x86 上 lock addl $0,0(%rsp) 實現;JMM 只在必要處插入(如 volatile 寫後、解鎖前)。

五、 Happens-Before:JMM 面向程序員的核心契約

定義:若 A hb B,則 A 的執行結果對 B 可見,且 A 在順序上排在 B 之前。不要求 A 物理執行在 B 之前(允許重排序,只要語義一致)。

核心 HB 規則

  1. 程序順序規則:線程內前操作 hb 後操作
  2. 監視器鎖規則:解鎖 hb 後續同一鎖的加鎖
  3. volatile 變量規則:寫 volatile hb 後續讀同一 volatile
  4. 線程啟動規則:Thread.start() hb 線程內任何操作
  5. 線程終止規則:線程內操作 hb Thread.join() 返回 / isAlive()=false
  6. 中斷規則:interrupt() hb 被中斷線程檢測到中斷
  7. 對象構造規則:構造函數結束 hb finalize() 開始
  8. 傳遞性:A hb B ∧ B hb C ⇒ A hb C

HB 與重排序規則的對應關係


volatile 寫  ──HB──→ volatile 讀
    │                   │
    ▼                   ▼
StoreStore屏障      LoadLoad屏障
StoreLoad屏障       LoadStore屏障
    │                   │
    ▼                   ▼
禁止 寫寫/寫讀重排   禁止 讀讀/讀寫重排

程序員只需記住 HB 規則,無需背誦底層屏障插入細節。JMM 保證:遵守 HB 規則的程序,在任意硬件/編譯器上表現一致。

六、 經典案例:雙重檢查鎖失效與修復


// 錯誤:instance = new Singleton() 非原子,分三步:
// 1. allocate memory  2. initialize object  3. reference assign
// 重排序可能 1→3→2,導致其他線程看到非 null 但未初始化完成的對象
public class Singleton {
    private static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) instance = new Singleton();
            }
        }
        return instance;
    }
}

// 修復:volatile 禁止 1→3→2 重排序
private static volatile Singleton instance;

七、 總結:JMM 三大核心保證

特性定義關鍵手段
可見性一線程寫入,另一線程能讀到最新值volatile、synchronized、final、HB 規則
有序性操作執行順序符合預期HB 規則、屏障指令、as-if-serial
原子性操作不可分割synchronized、Lock、原子類、單次讀寫 volatile/基本類型

深入理解 JMM,是從「會用併發 API」進階到「能設計正確併發系統、能診斷疑難併發 Bug」的必經之路。

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