【深入剖析】攻克 Java 并發的基石:Java 內存模型 (JMM) 原理與實踐指南

0.引言

理解 JMM (Java Memory Model - JMM) 是掌握 Java 并發編程的關鍵,它定義了多線程環境下,線程如何與主內存以及彼此之間交互內存數據。

核心目標: JMM 旨在解決多線程編程中的三個核心問題:

  1. 原子性 (Atomicity): 一個操作是不可中斷的,要么全部執行成功,要么完全不執行。
  2. 可見性 (Visibility): 一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。
  3. 有序性 (Ordering): 程序執行的順序可能和代碼編寫的順序不一致(指令重排序),JMM 定義了在何種情況下這種重排序是被允許或禁止的。

1.第一層:淺顯易懂 - 為什么需要 JMM? (The Problem)

想象一下一個簡單的場景:

public class VisibilityProblem {private static boolean flag = false; // 共享變量private static int value = 0;        // 共享變量public static void main(String[] args) {// 線程 Anew Thread(() -> {while (!flag) { // 循環檢查 flag// 空循環,等待 flag 變為 true}System.out.println("Value: " + value); // 打印 value}).start();// 線程 Bnew Thread(() -> {value = 42;   // 步驟 1:設置 valueflag = true;  // 步驟 2:設置 flag}).start();}
}
  • 直覺期望: 線程 B 先設置 value = 42,然后設置 flag = true。線程 A 看到 flag 變為 true 后跳出循環,打印出 Value: 42
  • 現實問題:
    • 可見性問題: 線程 B 修改了 flagvalue,但這些修改可能只存在于線程 B 的 CPU 緩存或寄存器中,沒有立即寫回主內存。線程 A 在自己的緩存中看到的 flag 可能仍然是 false,導致它永遠無法跳出循環。即使跳出了循環,它看到的 value 也可能是 0 而不是 42
    • 有序性問題: 編譯器或處理器為了優化性能,可能會對指令進行重排序。線程 B 中的 flag = true 操作可能在 value = 42 之前執行。如果此時線程 A 看到了 flagtrue 而跳出循環,它看到的 value 就可能是未初始化的 0

JMM 就是為了解決這類在多線程環境下因緩存、指令重排序等優化帶來的不可預測行為而制定的規則。


2.第二層:核心概念 - JMM 的抽象模型 (The Abstraction)

JMM 定義了一個抽象的內存模型,它屏蔽了底層硬件的具體實現細節(如 CPU 緩存、緩存一致性協議),為 Java 程序員提供了一個統一的視圖:

  1. 主內存 (Main Memory):
    • 存儲所有共享變量(實例字段、靜態字段、數組元素)的原始值。
    • 是線程間共享的區域。
  2. 工作內存 (Working Memory / Local Memory):
    • 每個線程都有自己的工作內存。
    • 存儲該線程使用到的共享變量的副本
    • 線程對共享變量的所有操作(讀取、賦值等)都必須在工作內存中進行,不能直接讀寫主內存
    • 工作內存是 JMM 的一個抽象概念,它涵蓋了 CPU 寄存器、各級緩存、寫緩沖區等硬件優化。

內存交互操作 (8 種原子操作)

JMM 定義了 8 種原子操作來描述線程、工作內存和主內存之間的交互:

  • lock (鎖定):作用于主內存變量,標識其為線程獨占狀態。
  • unlock (解鎖):作用于主內存變量,釋放鎖定狀態。
  • read (讀取):作用于主內存變量,將變量值從主內存傳輸到線程的工作內存。
  • load (載入):作用于工作內存變量,將 read 操作得到的值放入工作內存的變量副本中。
  • use (使用):作用于工作內存變量,將變量值傳遞給執行引擎(如進行計算)。
  • assign (賦值):作用于工作內存變量,將執行引擎接收到的值賦給工作內存變量。
  • store (存儲):作用于工作內存變量,將變量值從工作內存傳輸到主內存。
  • write (寫入):作用于主內存變量,將 store 操作傳輸過來的值放入主內存變量中。

規則: JMM 規定這些操作必須滿足特定的順序和約束(如 readloadstorewrite 必須成對按順序出現),但允許在成對操作之間插入其他操作(這是導致可見性和有序性問題的根源之一)。更關鍵的規則體現在 happens-before 原則上。


3.第三層:關鍵機制 - Happens-Before (HB)

happens-before 是 JMM 的核心概念,它定義了兩個操作之間的偏序關系。如果操作 A happens-before 操作 B,那么:

  1. 可見性保證: A 對共享變量的修改(結果)一定對 B 可見。
  2. 有序性保證: A 在程序順序上一定排在 B 之前執行(禁止某些重排序)。

注意: happens-before 并不一定意味著時間上的先后!它強調的是可見性和順序的保證。如果兩個操作之間沒有 happens-before 關系,JVM 可以隨意對它們進行重排序。

3.1JMM 定義的天然 Happens-Before 規則

  1. 程序次序規則 (Program Order Rule):同一個線程中,按照控制流順序(可能是分支、循環等),前面的操作 happens-before 后面的操作。
  2. 管程鎖定規則 (Monitor Lock Rule): 一個 unlock 操作 happens-before 于后續對同一個鎖lock 操作。
  3. volatile 變量規則 (Volatile Variable Rule): 對一個 volatile 變量的寫操作 happens-before 于后續對這個變量的讀操作。
  4. 線程啟動規則 (Thread Start Rule): Thread.start() 調用 happens-before 于被啟動線程中的任何操作。
  5. 線程終止規則 (Thread Termination Rule): 線程中的所有操作都 happens-before 于其他線程檢測到該線程已經終止(如 Thread.join() 返回成功或 Thread.isAlive() 返回 false)。
  6. 線程中斷規則 (Thread Interruption Rule): 對線程 interrupt() 的調用 happens-before 于被中斷線程檢測到中斷事件(拋出 InterruptedException 或調用 isInterrupted()/interrupted())。
  7. 對象終結規則 (Finalizer Rule): 一個對象的初始化完成(構造函數執行結束)happens-before 于它的 finalize() 方法的開始。
  8. 傳遞性 (Transitivity): 如果 A happens-before B,且 B happens-before C,那么 A happens-before C。

happens-before 的意義: 程序員只需要利用這些規則(主要是通過 synchronizedvolatilefinal 等關鍵字以及 java.util.concurrent 包中的工具),就能確保多線程操作的可見性和有序性,無需關心底層復雜的緩存和重排序細節。


4.第四層:深入剖析 - volatile 關鍵字

volatile 是 JMM 中最重要的關鍵字之一,它提供了比 synchronized 更輕量級的同步機制。

4.1volatile 的語義

  1. 保證可見性:
    • 對一個 volatile 變量的寫操作,會立即刷新到主內存
    • 對一個 volatile 變量的讀操作,會從主內存中讀取最新的值(或保證看到最近寫入的值)。
    • 這通過禁止編譯器/處理器對 volatile 變量的讀寫操作進行緩存優化來實現。
  2. 禁止指令重排序 (部分):
    • 編譯器在生成字節碼時,會在 volatile 寫操作前后插入寫屏障 (StoreStore + StoreLoad)
    • volatile 讀操作前后插入讀屏障 (LoadLoad + LoadStore)
    • 寫屏障 (Store Barrier):
      • StoreStore: 確保在該屏障之前的所有普通寫操作都刷新到主內存(對其他線程可見)。
      • StoreLoad: 確保在該屏障之前volatile 寫操作都完成,并且刷新到主內存后,才能執行該屏障之后volatile 讀/寫操作(開銷較大,通常由 StoreLoad 承擔)。
    • 讀屏障 (Load Barrier):
      • LoadLoad: 確保在該屏障之后的所有操作(普通讀或 volatile 讀)都在該屏障之后的讀操作之前執行(禁止重排序),并且強制從主內存或最新緩存中加載數據。
      • LoadStore: 確保在該屏障之后的所有操作都在該屏障之后的寫操作之前執行(禁止重排序),并且這些寫操作的數據依賴在該屏障之前的讀操作已完成。
    • 這些屏障共同作用,確保了 volatile 變量讀寫操作相對于其前后代碼的相對順序,從而實現了 happens-before 規則中的有序性保證。

4.2volatile 的局限性

  • 不保證原子性: volatile 不能保證復合操作的原子性。例如 volatile int count; count++; 這個操作 (count++ 包含讀-改-寫三步) 在多線程下仍然是不安全的。需要使用 synchronizedAtomicInteger 等。

4.3volatile 的典型用法

  • 狀態標志位: 如開頭的例子,用 volatile boolean flag; 來安全地通知其他線程狀態改變。
  • 一次性安全發布 (One-Time Safe Publication): 利用 volatile 寫操作的 StoreStore 屏障,確保在發布對象引用之前,對象的初始化已經完全完成(構造函數結束)。
    public class Singleton {private static volatile Singleton instance; // volatile 保證安全發布private Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一次檢查 (無鎖)synchronized (Singleton.class) {if (instance == null) { // 第二次檢查 (加鎖)instance = new Singleton(); // volatile 寫}}}return instance;}
    }
    
    如果沒有 volatile,其他線程可能看到一個未初始化完成Singleton 對象(指令重排序導致引用賦值在構造函數完成之前發生)。
  • 獨立觀察 (Independent Observation): 定期發布觀察結果供其他程序使用。
  • 開銷較低的讀-寫鎖策略: 當讀遠多于寫時,可以用 volatile 保證寫操作的可見性,讀操作不需要加鎖(但寫操作需要額外的同步機制如 synchronized 或 CAS 來保證原子性)。

5.第五層:JMM 與并發編程實踐

理解 JMM 對于編寫正確、高效、可預測的并發程序至關重要:

  1. 優先使用高級并發工具: java.util.concurrent 包 (ConcurrentHashMap, ExecutorService, CountDownLatch, CyclicBarrier, AtomicXxx 類等) 是構建在 JMM 基礎之上的,它們通常比直接使用 synchronizedvolatile 更安全、更高效、更易用。理解 JMM 能讓你更好地理解和使用這些工具。
  2. 正確使用 synchronized: synchronized 塊不僅提供互斥(原子性),也提供強大的內存語義:進入 synchronized 塊(獲得鎖)相當于執行一個 volatile 操作(能看到之前持有該鎖的線程的所有修改),退出 synchronized 塊(釋放鎖)相當于執行一個 volatile 操作(將修改刷新到主內存)。這確保了臨界區內外操作的可見性和有序性。
  3. 理解 final 字段:final 修飾的字段在構造函數中初始化后,其值對其他線程是可見的(無需同步),前提是對象的引用本身是正確發布的(例如通過 volatilesynchronized 安全發布規則)。這是 JMM 對 final 的特殊保證。
  4. 避免過度同步: 不必要的同步會帶來性能開銷。理解 JMM 可以幫助你判斷何時需要同步(主要是為了保護共享可變狀態),何時可以避免。
  5. 警惕內存可見性導致的微妙 Bug: 很多并發 Bug 不是由競態條件引起的,而是由內存可見性問題引起的。理解 JMM 是診斷這類 Bug 的基礎。

6.總結

  • JMM 是什么? 一個規范,定義了多線程環境下 Java 程序如何與內存交互,確保程序在并發執行時的可見性有序性(以及通過其他機制如鎖保證的原子性)。
  • 核心問題: 解決因 CPU 緩存、指令重排序導致的可見性有序性問題。
  • 抽象模型: 主內存(共享)和工作內存(線程私有副本),定義了 8 種內存交互操作。
  • 核心機制: happens-before 關系。它定義了操作間的可見性和順序保證。JMM 定義了一系列天然規則(程序次序、鎖、volatile、線程啟動/終止等)。
  • volatile 關鍵字:
    • 保證可見性(寫立即刷新,讀獲取最新值)。
    • 通過內存屏障StoreStore, StoreLoad, LoadLoad, LoadStore禁止特定類型的指令重排序
    • 不保證原子性
  • 實踐意義: 理解 JMM 是編寫正確、高效并發 Java 程序的基礎。它解釋了高級并發工具的工作原理,指導你正確使用 synchronizedvolatilefinal,并幫助你診斷復雜的并發 Bug。

掌握 JMM 需要時間和實踐。從理解基本問題和抽象模型開始,逐步深入到 happens-beforevolatile 的細節,最終將其應用于并發編程實踐中,是學習 JMM 的有效路徑。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/909355.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/909355.shtml
英文地址,請注明出處:http://en.pswp.cn/news/909355.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Three.js】初識 Three.js

Threejs介紹 我們開發 webgl 主要是使用 threejs 這個庫,因為 webGL太難用,太復雜!但是現代瀏覽器都支持WebGL,這樣我們就不必使用Flash、Java等插件就能在瀏覽器中創建三維圖形。 threejs 它提供-一個很簡單的關于WebGL特性的J…

【經驗總結】ECU休眠后連續發送NM報文3S后ECU網絡才被喚醒問題分析

目錄 前言 正文 1.問題描述 2.問題分析 3.驗證猜想 4.總結 前言 ECU的上下電/休眠喚醒在ECU開發設計過程中最容易出問題且都為嚴重問題,最近在項目開發過程中遇到ECU休眠狀態下連續發送NM報文3S后才能喚醒CAN網絡的問題,解決問題比較順利,但分析過程中涉及到的網絡休…

企業架構框架深入解析:TOGAF、Zachman Framework、FEAF與Gartner EA Framework

執行摘要 企業架構(EA)是一項至關重要的實踐,它使組織能夠協調其業務戰略、運營流程和技術基礎設施,以實現整體戰略目標。企業架構框架作為結構化的方法論和綜合性工具,旨在管理企業級系統的固有復雜性,提…

數字化動態ID隨機水印和ID跑馬燈實現教育視頻防錄屏

摘要:數字化動態ID隨機水印和ID跑馬燈技術可以有效保護數字教育資源。動態水印將用戶信息隨機顯示在視頻上且不可去除,能追蹤錄屏者并震懾盜版行為。ID跑馬燈則自定義顯示觀看者信息,便于追蹤盜版源頭并提供法律證據。這些技術大幅增加盜版成…

< 自用文兒 騰訊云 VPS > Ubuntu 24 系統,基本設置

前言: 3 月份買的騰訊云的這臺 VPS,剛發現現在退款,只能返回 0 元。測試應用已經遷移到JD,清除內容太麻煩,重裝更簡單。 因為配合政策,國內的云主機都有兩個 IP 地址,一個內網,一個…

React ajax中的跨域以及代理服務器

Axios的誕生 為什么會誕生Axios?說到Axios我們就不得不說下Ajax。最初的舊瀏覽器頁面在向服務器請求數據時,由于返回的是整個頁面的數據,所以整個頁面都會強制刷新一下,這對于用戶來講并不是很友好。因為當我們只需要請求部分數據…

HOT 100 | 73.矩陣置零、54.螺旋矩陣、48.旋轉圖像

一、73. 矩陣置零 73. 矩陣置零 - 力扣(LeetCode) 1. 解題思路 1. 使用兩個數組分別標記每行每列是否有0,初始化全為False,遇到0就變成True。 2. 遍歷矩陣,遇到0就將False改成True。 3. 再次遍歷矩陣,更…

神經網絡壓縮

網絡壓縮技術學習筆記 以下筆記基于提供的 PDF 文件(tiny_v7.pdf),總結了網絡壓縮技術的核心概念、實現原理和方法,特別針對多模態大模型、空間智能以及未來智能體(Agent)和通用人工智能(AGI&a…

論索引影響性能的一面④ 索引失蹤之謎【上】

梁敬彬梁敬弘兄弟出品 往期回顧 論索引影響性能的一面①索引的各種開銷 論索引影響性能的一面②索引的使用失效 論索引影響性能的一面③ 索引開銷與經典案例 開篇:DBA的深夜“尋人啟事” 作為數據庫的守護者,我們最信賴的伙伴莫過于“索引”。它如同一…

java集合(九) ---- Stack 類

目錄 九、Stack 類 9.1 位置 9.2 特點 9.3 棧 9.4 構造方法 9.5 常用方法 9.6 注意點:循環遍歷 Stack 類 九、Stack 類 9.1 位置 Stack 類位于 java.util 包下 9.2 特點 Stack 類是 Vector 類的子類Stack 類對標于數據結構中的棧結構 9.3 棧 定義&…

ARXML可視化轉換工具使用說明

ARXML可視化轉換工具 | 詳細使用指南與說明 📝 前言 自上篇文章《聊聊ARXML解析工具:我們是如何擺脫昂貴商業軟件的》發布以來,收到了眾多朋友的關注和咨詢,這讓我倍感榮幸! 新朋友請注意:如果您還沒有閱…

松勝與奧佳華按摩椅:普惠科技與醫療級體驗的碰撞

在智能健康設備快速普及的今天,按摩椅已從奢侈品轉變為家庭健康管理的重要工具。面對市場上琳瑯滿目的品牌,松勝與奧佳華憑借截然不同的發展路徑,各自開辟出特色鮮明的賽道:前者以“技術普惠”理念打破高端按摩椅的價格壁壘&#…

一起學習Web 后端——PHP(二):深入理解字符與函數的使用

一、前言 在上一講中,我們主要講PHP的相關知識。本節我們將繼續深入,學習: PHP 中各種語法字符、符號的含義與用法; PHP 中常用函數的種類、定義方式與實際應用。 這些知識是構建 Web 后端邏輯的基礎,對于后期編寫…

【Bluedroid】藍牙啟動之 GAP_Init 流程源碼解析

藍牙 GAP(通用訪問配置文件)模塊是藍牙協議棧的核心組件,負責設備發現、連接管理及基礎屬性暴露等關鍵功能。本文圍繞 Android藍牙協議棧 GAP 模塊的初始化流程與連接管理實現展開,結合代碼解析其核心函數(GAP_Init、gap_conn_init、gap_attr_db_init)的功能邏輯,以及關…

最新四六級寫作好詞好句錦囊(持續更新中)

完整版四六級備考攻略可見另一篇博客~~(喜歡的留個點贊收藏再走唄~~) ??????四六級備考攻略-CSDN博客 一、通用 1、詞組 2、單詞 3、句型 二、老齡化、老年人 三、學習、社交、社會實踐 四、文化、習俗 五、數字素養、數字技能 六、資…

Java 通用實體驗證框架:從業務需求到工程化實踐【生產級 - 適用于訂單合并前置校驗】

Java 通用實體驗證框架:從業務需求到工程化實踐【適用于訂單合并前置校驗】 一、業務驗證痛點與需求背景 1. 傳統驗證方式的困境 傳統驗證方式存在代碼冗余、維護成本高和擴展性差等問題。相同的驗證邏輯在不同模塊重復編寫,修改驗證規則時需要同步修…

PyArk飄云閣出品的ARK工具

PyArk是由飄云閣(PiaoYunGe)開發的一款功能強大的系統安全分析工具,主要用于Windows環境下的內核級檢測與分析。該工具集成了進程管理、驅動模塊掃描、內核及應用層鉤子檢測、進程注入等核心功能,旨在幫助安全研究人員深入識別潛在…

【高中數學之復數】已知復數z的幅角為60°,且|z-1|是|z|和|z-2|的等比中項,求|z|?(2003高考數學全國卷,解答題首題,總第17題)

【問題】 已知復數z的幅角為60,且|z-1|是|z|和|z-2|的等比中項,求|z|? 【來源】 2003高考數學全國卷,解答題首題,總第17題。 【解答】 解: 由復數輻長輻角定義有 zr*(Cos60iSin60) 據等比中項定義有&#xff1…

觀點 | 科技企業到了品牌建設的歷史性窗口期

隨著全球科技產業的飛速發展,科技型企業作為推動技術創新和經濟發展的重要力量,正面臨著前所未有的機遇與挑戰。近年來,中國科技行業保持了快速增長的態勢。根據國家統計局的數據,2023年全國研究與試驗發展(R&D&am…

影像組學5:Radiomics Score的計算

Rad-score(全稱 Radiomics score,影像組學評分)是通過數學模型將影像組學提取的多個特征整合為一個綜合性指標,從而簡化臨床分析與決策。 前文已介紹影像組學的病灶分割、特征提取及篩選流程,本節將重點闡述 Rad-scor…