溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解JMM內(nèi)存模型

發(fā)布時間:2021-09-29 16:14:27 來源:億速云 閱讀:127 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“如何理解JMM內(nèi)存模型”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

1.計算機內(nèi)存模型

CPU在執(zhí)行的時候,肯定要有數(shù)據(jù),而數(shù)據(jù)在內(nèi)存中放著呢,這里的內(nèi)存就是計算機的物理內(nèi)存,剛開始還好,但是隨著技術(shù)的發(fā)展,CPU處理的速度越來越快,而從內(nèi)存中讀取和寫入數(shù)據(jù)的過程和CPU的執(zhí)行速度比起來差距就會越來越大,所說設(shè)計師,就在物理內(nèi)存與CPU之間,加入了緩存的概念:也就是CPU在運行的時候,會將運算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中,那么CPU進(jìn)行計算時就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù),當(dāng)運算結(jié)束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。

(說到這里,你應(yīng)該能想到在高并發(fā),使用多線程處理的時候,存在的問題。)

單核CPU只含有一套L1,L2,L3緩存;如果CPU含有多個核心,即多核CPU,則每個核心都含有一套L1(甚至和L2)緩存,而共享L3(或者和L2)緩存。

當(dāng)你的計算式是:

單核CPU,單線程。 核心的緩存只被一個線程訪問。緩存獨占,不會出現(xiàn)訪問沖突等問題。

單核CPU,多線程。 進(jìn)程中的多個線程會同時訪問進(jìn)程中的共享數(shù)據(jù),CPU將某塊內(nèi)存加載到緩存后,不同線程在訪問相同的物理地址的時候,都會映射到相同的緩存位置,這樣即使發(fā)生線程的切換,緩存仍然不會失效。但由于任何時刻只能有一個線程在執(zhí)行,因此不會出現(xiàn)緩存訪問沖突。

多核CPU,多線程。 每個核都至少有一個L1 緩存。多個線程訪問進(jìn)程中的某個共享內(nèi)存,且這多個線程分別在不同的核心上執(zhí)行,則每個核心都會在各自的caehe中保留一份共享內(nèi)存的緩沖。由于多核是可以并行的,可能會出現(xiàn)多個線程同時寫各自的緩存的情況,而各自的cache之間的數(shù)據(jù)就有可能不同。

2.JMM內(nèi)存模型

JMM全稱為Java Memory Model  java內(nèi)存模型,它只是一組規(guī)范,并不真實存在,(看好了,只是一組規(guī)范、一個定義),它描述的是一個規(guī)則。通過這組規(guī)范定義程序中的變量的訪問方式。以此來解決多核CPU多線程時造成的問題(請想一想為什么會是多核CPU多線程時)。

JMM規(guī)定了工作內(nèi)存、主內(nèi)存,而主內(nèi)存是共享區(qū)域,所有線程都可以訪問,工作內(nèi)存是每個線程的工作內(nèi)存,每個線程對變量的操作必須在自己的工作內(nèi)存中進(jìn)行。在運行時將變量從主內(nèi)存中拷貝到工作內(nèi)存中,對變量進(jìn)行操作,操作完后再將變量寫會主內(nèi)存,不能直接在主內(nèi)存中操作變量。再說一遍:這是規(guī)范,是java定義的一組程序運行時的規(guī)范??吹竭@,是不是發(fā)現(xiàn)與計算機的內(nèi)存模型特別像

3.JAVA內(nèi)存區(qū)域

在這里再說一下java的內(nèi)存區(qū)域:堆、棧、方法區(qū)、本地方法區(qū)、程序計數(shù)器

  1. 方法區(qū):線程共享區(qū)域,主要用于存儲虛擬機加載的類信息、常量、靜態(tài)變量等數(shù)據(jù)。

  2. 堆:線程共享區(qū)域,虛擬機啟動時創(chuàng)建,主要用于存放對象的實例,所以也是java垃圾回收最頻繁的一個區(qū)域

  3. 棧:線程私有區(qū)域,與線程同時創(chuàng)建,棧數(shù)量與線程數(shù)量相等,以棧幀定義,執(zhí)行每個方法時,都會創(chuàng)建一個棧幀存儲方法的信息:操作數(shù)棧、動態(tài)鏈接方法、返回值、返回地址等信息,每個方法執(zhí)行從調(diào)用到結(jié)束,對應(yīng)著這個棧幀的入棧出棧。

  4. 程序計數(shù)器、本地方法棧我們先不關(guān)心,有心者請自行學(xué)習(xí)。

如果你看到這,我覺得你應(yīng)該會疑惑,說JMM的時候,會什么要把計算機的內(nèi)存模型、JAVA的內(nèi)存區(qū)域都描述一下,稍后你就會知道。

在這里我還要再強調(diào)一遍,jmm內(nèi)存模型的主內(nèi)存和工作內(nèi)存與java內(nèi)存區(qū)域的堆、棧、方法區(qū)等不是同一個層次的,無法類比。一個是規(guī)范、規(guī)則,就相當(dāng)于校規(guī)似的。如果真要對應(yīng),那就如同你想的  棧---工作內(nèi)存,堆、方法區(qū)---主內(nèi)存。

現(xiàn)在我要將這些聯(lián)系起來了:

首先jmm是一組規(guī)范,是java提出來的規(guī)范,那么java在設(shè)計的時候,肯定也符合這組規(guī)范。我認(rèn)為java的內(nèi)存區(qū)域就是根據(jù)jmm規(guī)范設(shè)計的,所以上面說了,他們不屬于一個層次,無法類比,只能說是java內(nèi)存區(qū)域,符合jmm的規(guī)范。

然后我們也知道,線程是cpu調(diào)度的最小單元,也就是說cpu的內(nèi)核執(zhí)行線程,上面也說了,執(zhí)行方法,也就是一個棧幀入棧出棧的過程,那么cpu內(nèi)核執(zhí)行線程,就是操作的棧中的數(shù)據(jù),那么就是cpu內(nèi)核把棧中的數(shù)據(jù)拷貝到它的緩存中去運行。可能有點模糊,我認(rèn)為你就記住,cpu執(zhí)行時的數(shù)據(jù)就是棧中的數(shù)據(jù)。

你可能在想,那堆中的數(shù)據(jù)時怎么操作的?我認(rèn)為是這樣:看到這,我也認(rèn)為電腦面前的你知道了堆在棧中存的是一個地址,那么cpu內(nèi)核在運行時,不也是根據(jù)這個地址找到那個數(shù)據(jù)了嘛,對cpu來講,你就是一份數(shù)據(jù),在它面前你跟棧中的數(shù)據(jù)都是一樣的,都是數(shù)據(jù),然后加到它的緩存中。

如果你讀的有點蒙,那就請再讀幾遍,書讀百遍,其義自見:也就是說你在讀第一遍的時候,你根本就沒理解,你的腦子里只有讀過的字,并沒有理解到寫的含義,打個比方說:我說筷子,你腦海的潛意識中是筷子兩根棍的樣子,而不是“筷子”這兩個字。

然后,請回顧上面提到的兩個問題,我也在這貼出來一段代碼,以此來表示多核多線程產(chǎn)生的問題:

public class VolatileFaceThread{
    boolean isRunning = true;
    void m() {
        System.out.println("isRunning start");
        while(isRunning) {
        }
        System.out.println("isRunning end");
    }
    public static void main(String\[\] args) {
        VolatileFaceThread vft = new VolatileFaceThread();
        new Thread(vft :: m).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        vft.isRunning = false;
        System.out.println("update isRunning...");
    }
}

預(yù)期效果:新啟線程會一直循環(huán)下去

這段代碼,新啟的線程將會一直循環(huán)下去,不會被停止。試驗此段代碼時,如果有的實際效果是在主線程修改后,新啟的線程也跟著停止了,那么你的電腦可能1核在運行。(當(dāng)初我在這被卡了好幾天,讓同事運行時,它運行的實際效果就是預(yù)期效果)。

這就是因為,兩個線程被兩個內(nèi)核運行,他們把值讀取到自己的緩存中運行。而緩存是每個內(nèi)核私有的,主線程修改了值,對新啟線程來說是不可見的,故新啟線程會一直循環(huán)。

那怎么解決呢,就是讓線程可見唄,java中有這一個關(guān)鍵字:volatile-----內(nèi)存可見性、禁止指令重排序。

被volatile關(guān)鍵字修飾的變量對所有線程總是可見的,也就是在一個線程修改了一個被volatile關(guān)鍵字修飾變量的值,新值總是可以被其他線程立即得知。 

“如何理解JMM內(nèi)存模型”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI