最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 內(nèi)存模型 (JMM) 與 volatile 底層實現(xiàn)

 更新時間:2026年03月17日 09:32:29   作者:程途知微  
這篇文章詳細介紹了Java中的volatile關鍵字,從硬件底層到JVM規(guī)范,逐步講解了它的核心原理,包括可見性和禁止重排序,文章還探討了volatile的使用場景、常見誤區(qū)以及在實際工作中的應用,感興趣的朋友跟隨小編一起看看吧

在 Java 并發(fā)編程的江湖里,volatile 是最輕量級的同步機制,但也是最容易被誤用、最難講透的一個關鍵字。很多開發(fā)者能脫口而出“可見性”和“禁止重排序”,但若追問其底層驅動力是什么?為什么它不能保證原子性?往往就語焉不詳。

今天,我們就撥開迷霧,從硬件底層到 JVM 規(guī)范,一步步徹底講透 JMM 與 volatile。

1. 這篇文章要解決什么問題?

在多線程環(huán)境下,我們經(jīng)常會遇到一些“詭異”的現(xiàn)象:

  • 不可見性:線程 A 修改了一個全局變量,線程 B 卻一直讀到舊值,導致代碼邏輯死循環(huán)。
  • 亂序搞鬼:明明代碼寫的是先初始化對象再賦值給引用,結果線程 B 拿到了一個還沒初始化完的“半成品”對象(經(jīng)典 DCL 漏洞)。

這些現(xiàn)象背后的根源是 CPU 緩存不一致指令重排序。JMM(Java Memory Model)和 volatile 的出現(xiàn),就是為了給開發(fā)者提供一套標準的“契約”,確保在多線程環(huán)境下內(nèi)存交互的正確性。

2. 核心原理:為什么需要 JMM?

JMM 的抽象模型

Java 虛擬機規(guī)范定義了 JMM,目的是屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異。

JMM 規(guī)定:

  1. 主內(nèi)存(Main Memory):所有變量都存儲在主內(nèi)存中。
  2. 工作內(nèi)存(Working Memory):每個線程都有自己的工作內(nèi)存,保存了該線程使用到的變量的主內(nèi)存副本。

線程對變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存。

硬件背景:千里尋蹤 MESI 協(xié)議

為什么需要工作內(nèi)存?因為 CPU 太快了,內(nèi)存太慢了。為了彌補速度差,CPU 引入了多級緩存(L1/L2/L3)。

當多個 CPU 核心同時操作同一個內(nèi)存地址時,就會出現(xiàn)緩存不一致。硬件層面通過 MESI(Modified, Exclusive, Shared, Invalid)協(xié)議 來解決:

  • Modified:該行數(shù)據(jù)被修改,與主存不一致,需寫回。
  • Exclusive:該行數(shù)據(jù)僅由當前 CPU 持有,且與主存一致。
  • Shared:多核共享,與主存一致。
  • Invalid:該行數(shù)據(jù)失效,需從主存或其它核心重新加載。

volatile 在底層正是利用了觸發(fā)硬件緩存一致性的機制。

重排序:代碼并不總是按你想的運行

為了提高性能,從源代碼到執(zhí)行指令,會經(jīng)歷三重重排序:

  1. 編譯器優(yōu)化重排序:編譯器在不改變單線程語義的前提下重排。
  2. 指令級并行重排序:CPU 將多條指令重疊執(zhí)行。
  3. 內(nèi)存系統(tǒng)重排序:由于緩存和讀寫緩沖區(qū)的存在,加載和存儲看起來是亂序的。

3. 流程/機制描述:volatile 是如何工作的?

內(nèi)存屏障(Memory Barrier)

JVM 會在 volatile 變量讀寫前后插入 內(nèi)存屏障,它是一組處理器指令,用于限制編譯器和處理器的重排序。

JMM 的屏障規(guī)則非常嚴苛:

  • StoreStore 屏障:在 volatile 寫之前插入,禁止前面的普通寫和 volatile 寫重排序。
  • StoreLoad 屏障:在 volatile 寫之后插入,保證該寫操作對所有處理器可見(開銷最大,最關鍵)。
  • LoadLoad 屏障:在 volatile 讀之后插入,禁止后面所有普通讀操作和該讀重排序。
  • LoadStore 屏障:在 volatile 讀之后插入,禁止后面所有普通寫操作和該讀重排序。

x86 底層:lock 前綴指令

在常見的 x86 架構 CPU 上,volatile 的底層實現(xiàn)其實是依靠一個 lock 前綴指令。 當 JVM 執(zhí)行帶有 volatile 的寫操作時,會生成的匯編代碼中會包含 lock addl $0x0, (%esp)(或者類似的空操作)。

這個 lock 前綴有兩大核心作用:

  1. 立即刷新主存:它會將該 CPU 核緩存行的數(shù)據(jù)立即寫回到系統(tǒng)內(nèi)存。
  2. 使其它緩存失效:由于 MESI 協(xié)議的嗅探機制,其它 CPU 核心會監(jiān)聽到該數(shù)據(jù)的變化,并將其對應的緩存行設置為 Invalid 狀態(tài)。下次其它核心讀取時,強制去主存加載。

4. 關鍵代碼/示例

場景一:可見性演示

如果不用 volatile,這個程序可能永遠不會停止。

import java.util.concurrent.TimeUnit;
/**
 * 可見性案例:Flag 標記位
 */
public class VisibilityDemo {
    // 若不加 volatile,主線程修改 stop 標記后,workThread 可能永遠感知不到
    private static volatile boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        Thread workThread = new Thread(() -> {
            System.out.println("工作線程啟動...");
            while (!stop) {
                // 循環(huán)執(zhí)行業(yè)務
            }
            System.out.println("工作線程感知到停止信號,退出循環(huán)。");
        });
        workThread.start();
        // 睡眠 1 秒確保工作線程已經(jīng)進入循環(huán)
        TimeUnit.SECONDS.sleep(1);
        stop = true;
        System.out.println("主線程已修改 stop 標記為 true");
    }
}

場景二:禁止重排序(DCL 單例)

這是 volatile 在企業(yè)級應用中最經(jīng)典的場景。

可通過高并發(fā)模擬驗證,或使用JCStress進行驗證

/**
 * 雙重檢查鎖定(DCL)單例模式
 */
public class Singleton {
    // 必須加 volatile,防止指令重排序
    private static volatile Singleton instance;
    private Singleton() {
        // 初始化邏輯
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    /*
                     * 重點:new Singleton() 包含三步:
                     * 1. 分配內(nèi)存空間
                     * 2. 執(zhí)行構造方法初始化對象
                     * 3. 將 instance 指向分配的內(nèi)存空間
                     * 
                     * 若無 volatile,2 和 3 可能重排序。
                     * 線程 A 執(zhí)行了 1、3,還未執(zhí)行 2 時,線程 B 判斷 instance 不為空,
                     * 于是拿到了一個未完成初始化的“空殼”對象,造成空指針異常。
                     */
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

5. 常見誤區(qū)

誤區(qū) 1:volatile 保證原子性

絕對錯誤! volatile 只保證可見性和有序性。對于類似 i++ 這種操作(包含:讀取、加一、寫回),它無法保證三步操作的整體原子性。多線程下依然會出現(xiàn)覆寫。 對策:使用 AtomicIntegersynchronized

誤區(qū) 2:volatile 性能非常差

片面。 volatile 的寫操作由于需要插入 StoreLoad 屏障刷新緩存,確實比普通寫慢。但在讀操作上,由于現(xiàn)代 CPU 的優(yōu)化,其開銷非常接近普通讀。它比 synchronized 這種重量級鎖要快得多。

6. 實際工作中怎么用?

  1. 狀態(tài)標志位:如上面的 stop 標記,用于優(yōu)雅退出線程。
  2. 多線程環(huán)境下的單次賦值:如 DCL 單例中防止拿到半初始化對象。
  3. “Happens-Before” 傳遞性配合: JMM 規(guī)定,如果你先寫一個 volatile 變量,再由另一個線程讀這個變量,那么寫之前的所有可見修改對讀之后的線程都是可見的。你可以利用這一點,通過修改一個 volatile 變量來“順帶”發(fā)布一組其它變量。

總結

volatile 是深入理解 JVM 內(nèi)存模型的入場券。它就像是 CPU 緩存一致性協(xié)議在 Java 層的投影,通過內(nèi)存屏障和硬件指令,在紛亂的并發(fā)世界中強行劃定了一道名為“確定性”的邊界。

作為資深開發(fā)者,理解它不僅是為了寫出高性能的代碼,更是為了掌握系統(tǒng)底層的運行規(guī)律,在面對復雜的并發(fā)難題時,能一眼看穿真相。

到此這篇關于Java 內(nèi)存模型 (JMM) 與 volatile 底層實現(xiàn)的文章就介紹到這了,更多相關Java 內(nèi)存模型 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java 如何將網(wǎng)絡資源url轉化為File文件

    Java 如何將網(wǎng)絡資源url轉化為File文件

    這篇文章主要介紹了Java 如何將網(wǎng)絡資源url轉化為File文件的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • spring aop的簡單使用方法詳解

    spring aop的簡單使用方法詳解

    這篇文章主要介紹了spring aop的簡單使用方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-10-10
  • Java8如何使用Lambda表達式簡化代碼詳解

    Java8如何使用Lambda表達式簡化代碼詳解

    這篇文章主要給大家介紹了關于Java8如何使用Lambda表達式簡化的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • Java實現(xiàn)JS中的escape和UNescape代碼分享

    Java實現(xiàn)JS中的escape和UNescape代碼分享

    在PHP和Python中都有類似JS中的escape和UNescape函數(shù)的功能,那么Java語言中到底有沒有類似的方法呢?本文就來介紹一下Java實現(xiàn)JS中的escape和UNescape轉碼方法,需要的朋友可以參考下
    2017-09-09
  • Java中注解@Async實現(xiàn)異步及導致失效原因分析

    Java中注解@Async實現(xiàn)異步及導致失效原因分析

    Async注解用于聲明一個方法是異步的,當在方法上加上這個注解時將會在一個新的線程中執(zhí)行該方法,而不會阻塞原始線程,這篇文章主要給大家介紹了關于Java中注解@Async實現(xiàn)異步及導致失效原因分析的相關資料,需要的朋友可以參考下
    2024-07-07
  • Java基礎知識之ByteArrayOutputStream流的使用

    Java基礎知識之ByteArrayOutputStream流的使用

    這篇文章主要介紹了Java基礎知識之ByteArrayOutputStream流的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • idea直接修改新的git地址的方法(圖文)

    idea直接修改新的git地址的方法(圖文)

    這篇文章主要介紹了idea直接修改新的git地址的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • Java實現(xiàn)的簡單字符串反轉操作示例

    Java實現(xiàn)的簡單字符串反轉操作示例

    這篇文章主要介紹了Java實現(xiàn)的簡單字符串反轉操作,結合實例形式分別描述了java遍歷逆序輸出以及使用StringBuffer類的reverse()方法兩種字符串反轉操作技巧,需要的朋友可以參考下
    2018-08-08
  • springboot實現(xiàn)獲取客戶端IP地址的示例代碼

    springboot實現(xiàn)獲取客戶端IP地址的示例代碼

    本文介紹了在SpringBoot中獲取客戶端IP地址的幾種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-11-11
  • Kafka中的producer攔截器與consumer攔截器詳解

    Kafka中的producer攔截器與consumer攔截器詳解

    這篇文章主要介紹了Kafka中的producer攔截器與consumer攔截器詳解,Producer 的Interceptor使得用戶在消息發(fā)送前以及Producer回調(diào)邏輯前有機會對消息做 一些定制化需求,比如修改消息等,需要的朋友可以參考下
    2023-12-12

最新評論

龙州县| 怀宁县| 延寿县| 庄浪县| 辽宁省| 库伦旗| 三亚市| 航空| 富蕴县| 湟中县| 黑龙江省| 波密县| 长岭县| 南昌市| 盐津县| 田东县| 墨竹工卡县| 平罗县| 饶阳县| 吐鲁番市| 英超| 紫云| 博客| 兴仁县| 南宁市| 南京市| 贺兰县| 濮阳县| 江阴市| 玉溪市| 多伦县| 甘谷县| 通榆县| 昌都县| 焦作市| 绿春县| 梨树县| 合山市| 航空| 杭锦旗| 即墨市|