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

Java中的內(nèi)存模型JMM詳細解讀

 更新時間:2023年12月12日 10:26:59   作者:程光CS  
這篇文章主要介紹了Java中的內(nèi)存模型JMM詳細解讀,Java?對內(nèi)存的抽象模型如下,每個線程都有一塊自己的私有內(nèi)存(也稱為工作內(nèi)存),當線程使用變量時,會把主內(nèi)存里面的變量復制到工作內(nèi)存,線程讀寫變量時操作的是自己工作內(nèi)存中的變量,需要的朋友可以參考下

一、CPU緩存一致性問題

1. CPU緩存模型

CPU Cache 通常分為三級緩存:L1 Cache、L2 Cache、L3 Cache,級別越低的離 CPU 核心越近,訪問速度也快,但是存儲容量相對就會越小。其中,在多核心的 CPU 里,每個核心都有各自的 L1/L2 Cache,而 L3 Cache 是所有核心共享使用的。

在這里插入圖片描述

2. MESI緩存一致性協(xié)議

多核CPU緩存則必然會有緩存與主存之間的一致性的問題,例如在核心1的L1/L2 cache中修改了某項數(shù)據(jù)但還沒寫回主存,那么核心2再讀取這項數(shù)據(jù)時則讀的舊的錯誤數(shù)據(jù)。

要想實現(xiàn)緩存一致性,要滿足以下兩點:

寫傳播:某個 CPU 核心里的 Cache 數(shù)據(jù)更新時,必須要傳播到其他核心的 Cache。

事務的串行化:多個 CPU 核心對一個數(shù)據(jù)的操作順序,必須在其他核心看起來順序是一樣的。

基于總線嗅探機制的緩存一致性協(xié)議 MESI 就滿足上面了這兩點。CPU緩存中的每塊數(shù)據(jù)中都有如下其中的一個狀態(tài)標記,當數(shù)據(jù)變化時通過總線嗅探監(jiān)聽機制使其它CPU核心感知到并修改緩存數(shù)據(jù)的狀態(tài):

在這里插入圖片描述

3. 弱緩存一致性

上述MESI協(xié)議雖然可以保證緩存的一致性,但又會影響性能,因此現(xiàn)代計算機中并不是完全遵守。關于這個問題的發(fā)展歷程如下:

CPU 從單核發(fā)展為多核,導致出現(xiàn)了多個核間的緩存一致性問題 --> 為了解決緩存一致性問題,提出了 MESI 協(xié)議 --> 完全遵守 MESI 又會給 CPU 帶來性能問題 --> CPU 設計者為了提高性能又在cache基礎上增加 store buffer 和 invalid queue --> 又導致了緩存的順序一致性變?yōu)榱巳蹙彺嬉恢滦?--> 需要緩存的順序一致性的,就需要軟件工程師自己在合適的地方添加內(nèi)存屏障,volatile 的作用之一就是給虛擬機看讓其在對應的指令加入內(nèi)存屏障。防止cpu級別的重排序,從而避免緩存一致性問題。

因此由于CPU弱緩存一致性的問題,在多線程中,一個線程對于一個共享變量的修改對其它線程可能是不可見的。

二、指令的重排序問題

指令重排序: 在不影響單線程程序執(zhí)行結果的前提下,計算機為了最大限度的發(fā)揮機器性能,會對機器指令重排序優(yōu)化 Java 源代碼會經(jīng)歷 編譯器優(yōu)化重排 —> 指令并行重排 —> 內(nèi)存系統(tǒng)重排 的過程,最終才變成操作系統(tǒng)可執(zhí)行的指令序列。

指令重排序會保證串行語義一致,但是沒有義務保證多線程間的語義也一致 ,所以在多線程下,指令重排序可能會導致一些問題。

例如如下創(chuàng)建單例對象的代碼

uniqueInstance = new Singleton(); 

這段代碼其實是分為三步執(zhí)行:

  1. 為 uniqueInstance 分配內(nèi)存空間
  2. 初始化uniqueInstance
  3. 將 uniqueInstance指向分配的內(nèi)存地址

但是由于 JVM 具有指令重排的特性,執(zhí)行順序有可能變成 1->3->2。指令重排在單線程環(huán)境下不會出現(xiàn)問題,但是在多線程環(huán)境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執(zhí)行了 1 和 3,此時 T2 調(diào)用 getUniqueInstance() 后發(fā)現(xiàn) uniqueInstance 不為空,因此返回 uniqueInstance,但此時 uniqueInstance 還未被初始化,從而導致出錯。

三、Java 內(nèi)存模型(JMM)詳解

Java是跨平臺的,為解決不同平臺下上述CPU弱緩存一致性帶來的共享變量可見性以及指令的重排序等問題,并且方便程序員更加安全高效地實現(xiàn)多線程編程,Java提供一套內(nèi)存模型以及并發(fā)編程規(guī)范以屏蔽系統(tǒng)差異。

對于 Java 開發(fā)者說,你不需要了解底層原理,直接使用并發(fā)相關的一些關鍵字和類(比如 volatile、synchronized、各種 Lock)即可開發(fā)出并發(fā)安全的程序。

1. Java 內(nèi)存模型

Java 對內(nèi)存的抽象模型如下,每個線程都有一塊自己的私有內(nèi)存(也稱為工作內(nèi)存),當線程使用變量時,會把主內(nèi)存里面的變量復制到工作內(nèi)存,線程讀寫變量時操作的是自己工作內(nèi)存中的變量。線程的工作內(nèi)存實際上就是對CPU緩存和寄存器的統(tǒng)一抽象。

在這里插入圖片描述

為實現(xiàn)線程工作內(nèi)存與主內(nèi)存的同步,Java規(guī)范在內(nèi)存模型中定義了以下八種同步操作(了解即可,無需死記硬背):

  • lock(鎖定): 作用于主內(nèi)存中的變量,將他標記為一個線程獨享變量。
  • unlock(解鎖): 作用于主內(nèi)存中的變量,解除變量的鎖定狀態(tài),被解除鎖定狀態(tài)的變量才能被其他線程鎖定。
  • read(讀?。鹤饔糜谥鲀?nèi)存的變量,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的 load 動作使用。
  • load(載入):把 read 操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量的副本中。
  • use(使用):把工作內(nèi)存中的一個變量的值傳給執(zhí)行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令。
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
  • store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存中,以便隨后的 write 操作使用。
  • write(寫入):作用于主內(nèi)存的變量,它把 store 操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

我們在編寫程序代碼時使用volatile、synchronized和各種 Lock等關鍵字即可間接實現(xiàn)這些同步操作來解決前面提到的在多線程中可能會出現(xiàn)的問題。

以如下程序為例:

public class JMMTest {
    private boolean initFlag = false;
//    private volatile boolean initFlag = false;//volatile關鍵字可保證變量的可見性以及指令的有序性
    public static void main(String[] args) throws InterruptedException {
        JMMTest jmmTest = new JMMTest();
        new Thread(() -> {
            System.out.println("Thread1-start");
            //線程2對flag的修改對線程1不可見,故會陷入死循環(huán)
            while (!jmmTest.initFlag){
            }
            System.out.println("Thread1-end");
        }).start();
        Thread.sleep(100);
        new Thread(() -> {
            System.out.println("Thread2-start");
            jmmTest.initFlag = true;
            System.out.println("Thread2-end");
        }).start();
    }
}

當成員變量initFlag沒有用volatile修飾時,線程1首先用read操作從主內(nèi)存中讀取initFlag的值,然后用load操作加載到工作內(nèi)存的副本中,再用use操作使用值后進入循環(huán),輪到線程2執(zhí)行,也是先read -> load -> use,然后用assign操作將從執(zhí)行引擎接收到的true值復制給工作內(nèi)存中的initFlag副本,最后在某個時候用store -> write操作寫入主內(nèi)存。但是此時線程1仍然讀取的是其工作內(nèi)存中的值,因此就陷入了死循環(huán)。

在這里插入圖片描述

當成員變量initFlag使用volatile修飾后,線程2修改initFlag后會立即寫回主內(nèi)存并且讓線程1中的變量副本失效,因此線程1需要從主內(nèi)存中重新讀取最新的值,以此實現(xiàn)了變量的可見性,從而能夠退出循環(huán)。

在這里插入圖片描述

2. 內(nèi)存屏障

內(nèi)存屏障表示隔開兩個內(nèi)存同步操作,使其能夠有序執(zhí)行而不被重排序

在這里插入圖片描述

在這里插入圖片描述

內(nèi)存屏障只是一種規(guī)范,真正落地的實現(xiàn)屬于底層的細節(jié),比如volatile的內(nèi)存屏障底層是通過lock匯編指令實現(xiàn)的。

3. happens-before 原則

happens-before 原則表示程序中某些指令操作必發(fā)生在另一些指令操作前面,不允許重排序。

happens-before 原則的設計思想:

為了對編譯器和處理器的約束盡可能少,只要不改變程序的執(zhí)行結果(單線程程序和正確執(zhí)行的多線程程序),編譯器和處理器怎么進行重排序優(yōu)化都行。

  • 對于會改變程序執(zhí)行結果的重排序,JMM 要求編譯器和處理器必須禁止這種重排序。
  • happens-before 的規(guī)則共 8 條,重點了解下面5 條即可:
  1. 程序順序規(guī)則 :一個線程內(nèi),按照代碼順序,書寫在前面的操作 happens-before 于書寫在后面的操作;
  2. 解鎖規(guī)則 :解鎖 happens-before 于加鎖;
  3. volatile 變量規(guī)則 :對一個 volatile 變量的寫操作 happens-before 于后面對這個 volatile 變量的讀操作。說白了就是對 volatile 變量的寫操作的結果對于發(fā)生于其后的任何操作都是可見的。
  4. 傳遞規(guī)則 :如果 A happens-before B,且 B happens-before C,那么 A happens-before C;
  5. 線程啟動規(guī)則 :Thread 對象的 start()方法 happens-before 于此線程的每一個動作。

如果兩個操作不滿足任意一個 happens-before 規(guī)則,那么這兩個操作就沒有順序的保障,JVM 可以對這兩個操作進行重排序。程序員則基于happens-before規(guī)則提供的內(nèi)存可見性保證來編程。

四、并發(fā)編程的三個重要特性

1. 原子性

原子性:一次操作或者多次操作,要么所有的操作全部都得到執(zhí)行并且不會受到任何因素的干擾而中斷,要么都不執(zhí)行。 在 Java 中,可以借助synchronized 、各種 Lock 以及各種原子類實現(xiàn)原子性。 synchronized 和各種 Lock 可以保證任一時刻只有一個線程訪問該代碼塊,因此可以保障原子性。各種原子類是利用 CAS (compare and swap) 操作(可能也會用到 volatile或者final關鍵字)來保證原子操作。

2. 可見性

可見性:當一個線程對共享變量進行了修改,那么另外的線程都是立即可以看到修改后的最新值。 在 Java 中,可以借助synchronized 、volatile 以及各種 Lock 實現(xiàn)可見性。如果我們將變量聲明為 volatile ,這就指示 JVM,這個變量是共享且不穩(wěn)定的,每次使用它都到主存中進行讀取。

3. 有序性

有序性:指令執(zhí)行順序在并發(fā)環(huán)境下依然能按預期執(zhí)行,不會因為重排序而產(chǎn)生錯亂。 由于指令重排序問題,代碼的執(zhí)行順序未必就是編寫代碼時候的順序。我們上面講重排序的時候也提到過:指令重排序可以保證串行語義一致,但是沒有義務保證多線程間的語義也一致 ,所以在多線程下,指令重排序可能會導致一些問題。在 Java 中,volatile 關鍵字可以禁止指令進行重排序優(yōu)化。

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

相關文章

  • Java進階之FileUpload完成上傳的實例

    Java進階之FileUpload完成上傳的實例

    這篇文章主要介紹了 Java進階之FileUpload完成上傳的實例的相關資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • Java 向上轉型和向下轉型的詳解

    Java 向上轉型和向下轉型的詳解

    這篇文章主要介紹了 Java 向上轉型和向下轉型的詳解的相關資料,需要的朋友可以參考下
    2017-04-04
  • 解決mybatis中的mapper命名問題

    解決mybatis中的mapper命名問題

    這篇文章主要介紹了解決mybatis中的mapper命名問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • SpringBoot基于AOP的Controller實現(xiàn)統(tǒng)一日志打印的完整方案

    SpringBoot基于AOP的Controller實現(xiàn)統(tǒng)一日志打印的完整方案

    本文基于Spring?AOP實現(xiàn)一套無侵入、可配置、靈活擴展的Controller層統(tǒng)一日志打印方案,自動記錄接口全量信息,同時支持慢接口監(jiān)控告警,完全滿足開發(fā)排查問題的核心需求
    2026-04-04
  • Spring多定時任務@Scheduled執(zhí)行阻塞問題解決

    Spring多定時任務@Scheduled執(zhí)行阻塞問題解決

    這篇文章主要介紹了Spring多定時任務@Scheduled執(zhí)行阻塞問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • SpringBoot自動配置源碼深入刨析講解

    SpringBoot自動配置源碼深入刨析講解

    這篇文章主要介紹了SpringBoot自動配置原理分析,SpringBoot是我們經(jīng)常使用的框架,那么你能不能針對SpringBoot實現(xiàn)自動配置做一個詳細的介紹。如果可以的話,能不能畫一下實現(xiàn)自動配置的流程圖。牽扯到哪些關鍵類,以及哪些關鍵點
    2022-09-09
  • Java手寫線程池之向JDK線程池進發(fā)

    Java手寫線程池之向JDK線程池進發(fā)

    在前面的文章自己動手寫乞丐版線程池中,我們寫了一個非常簡單的線程池實現(xiàn),這個只是一個非常簡單的實現(xiàn),在本篇文章當中我們將要實現(xiàn)一個和JDK內(nèi)部實現(xiàn)的線程池非常相似的線程池,需要的可以了解一下
    2022-10-10
  • SpringBoot+logback默認日志的配置和使用方式

    SpringBoot+logback默認日志的配置和使用方式

    這篇文章主要介紹了SpringBoot+logback默認日志的配置和使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • SpringBoot項目中@Test不出現(xiàn)可點擊運行的按鈕問題

    SpringBoot項目中@Test不出現(xiàn)可點擊運行的按鈕問題

    這篇文章主要介紹了SpringBoot項目中@Test不出現(xiàn)可點擊運行的按鈕問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java?接口定義變量的示例代碼

    Java?接口定義變量的示例代碼

    文章介紹了Java接口中的變量和方法,接口中的變量必須是publicstaticfinal的,用于定義常量,而方法默認是publicabstract的,必須由實現(xiàn)類來實現(xiàn),接口不能實例化,只能通過實現(xiàn)類來實現(xiàn)接口中的方法,本文介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2025-12-12

最新評論

云梦县| 澄江县| 县级市| 华容县| 仁化县| 库尔勒市| 塔河县| 徐汇区| 湖北省| 林州市| 新安县| 通化县| 许昌县| 剑川县| 临泉县| 大庆市| 咸阳市| 璧山县| 南开区| 孟津县| 屯昌县| 丘北县| 安乡县| 绥棱县| 普兰店市| 昂仁县| 砚山县| 沁源县| 昌吉市| 焉耆| 久治县| 靖宇县| 林周县| 泉州市| 台安县| 客服| 策勒县| 安龙县| 望江县| 苗栗县| 溆浦县|