欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

一、CPU緩存一致性問題

1. CPU緩存模型

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

在這里插入圖片描述

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

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

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

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

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

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

在這里插入圖片描述

3. 弱緩存一致性

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

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

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

二、指令的重排序問題

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

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

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

uniqueInstance = new Singleton(); 

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

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

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

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

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

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

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

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

在這里插入圖片描述

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

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

我們?cè)诰帉懗绦虼a時(shí)使用volatile、synchronized和各種 Lock等關(guān)鍵字即可間接實(shí)現(xiàn)這些同步操作來解決前面提到的在多線程中可能會(huì)出現(xiàn)的問題。

以如下程序?yàn)槔?/p>

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

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

在這里插入圖片描述

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

在這里插入圖片描述

2. 內(nèi)存屏障

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

在這里插入圖片描述

在這里插入圖片描述

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

3. happens-before 原則

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

happens-before 原則的設(shè)計(jì)思想:

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

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

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

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

1. 原子性

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

2. 可見性

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

3. 有序性

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

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

相關(guān)文章

  • 詳解Spring中接口的bean是如何注入的

    詳解Spring中接口的bean是如何注入的

    這篇文章主要介紹了詳解Spring中接口的bean是如何注入的的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Java基于swing實(shí)現(xiàn)的彈球游戲代碼

    Java基于swing實(shí)現(xiàn)的彈球游戲代碼

    這篇文章主要介紹了Java基于swing實(shí)現(xiàn)的彈球游戲代碼,包含了窗體界面設(shè)計(jì)與游戲的邏輯功能處理,具有不錯(cuò)的參考借鑒價(jià)值,需要的朋友可以參考下
    2014-11-11
  • Spring實(shí)戰(zhàn)之XML與JavaConfig的混合配置詳解

    Spring實(shí)戰(zhàn)之XML與JavaConfig的混合配置詳解

    大家都知道Spring的顯示配置方式有兩種,一種是基于XML配置,一種是基于JavaConfig的方式配置。那么下這篇文章主要給大家分別介紹如何在JavaConfig中引用XML配置的bean以及如何在XML配置中引用JavaConfig,需要的朋友可以參考下。
    2017-07-07
  • SpringBoot測(cè)試時(shí)卡在Resolving Maven dependencies的問題

    SpringBoot測(cè)試時(shí)卡在Resolving Maven dependencies的問題

    這篇文章主要介紹了SpringBoot測(cè)試時(shí)卡在Resolving Maven dependencies的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Spring Boot2中如何優(yōu)雅地個(gè)性化定制Jackson實(shí)現(xiàn)示例

    Spring Boot2中如何優(yōu)雅地個(gè)性化定制Jackson實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了Spring Boot2中如何優(yōu)雅地個(gè)性化定制Jackson實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Java分布式鎖由淺入深介紹

    Java分布式鎖由淺入深介紹

    這篇文章主要介紹了Java分布式鎖,數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖方式比較多,如悲觀鎖(查詢時(shí)增加for?update)、樂觀鎖(通過version字段)、增加一個(gè)表記錄鎖信息等。因?yàn)橐蕾囉跀?shù)據(jù)庫,比較好理解,但是也存在一些問題
    2023-03-03
  • 淺談Java數(shù)組的一些使用方法及堆棧存儲(chǔ)

    淺談Java數(shù)組的一些使用方法及堆棧存儲(chǔ)

    下面小編就為大家?guī)硪黄獪\談Java數(shù)組的一些使用方法及堆棧存儲(chǔ)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • 利用Spring IOC技術(shù)實(shí)現(xiàn)用戶登錄驗(yàn)證機(jī)制

    利用Spring IOC技術(shù)實(shí)現(xiàn)用戶登錄驗(yàn)證機(jī)制

    這篇文章主要為大家詳細(xì)介紹了Spring IOC技術(shù)實(shí)現(xiàn)用戶登錄驗(yàn)證機(jī)制的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Spring事務(wù)失效的幾種原因

    Spring事務(wù)失效的幾種原因

    在日常編碼過程中常常涉及到事務(wù),在前兩天看到一篇文章提到了Spring事務(wù),那么在此總結(jié)下在Spring環(huán)境下事務(wù)失效的幾種原因.
    2020-09-09
  • SpringBoot實(shí)現(xiàn)OneDrive文件上傳的詳細(xì)步驟

    SpringBoot實(shí)現(xiàn)OneDrive文件上傳的詳細(xì)步驟

    這篇文章主要介紹了SpringBoot實(shí)現(xiàn)OneDrive文件上傳的詳細(xì)步驟,文中通過代碼示例和圖文講解的非常詳細(xì),對(duì)大家實(shí)現(xiàn)OneDrive文件上傳有一定的幫助,需要的朋友可以參考下
    2024-02-02

最新評(píng)論