JAVA內(nèi)存模型(JMM)
共享變量:如果一個變量在多個線程的工作內(nèi)存中都存在副本,那么這個變量就是這幾個線程的共享變量。
上面的工作內(nèi)存其實是java內(nèi)存模型抽象出來的概念,下面簡要介紹一下java內(nèi)存模型(JMM)。
java內(nèi)存模型(java memory model): 描述了java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量存儲到內(nèi)存和從內(nèi)存中讀取出變量這樣的底層細節(jié)。
不同的平臺,內(nèi)存模型是不一樣的,我們可以把內(nèi)存模型理解為在特定操作協(xié)議下,對特定的內(nèi)存或高速緩存進行讀寫訪問的過程抽象。Java 虛擬機規(guī)范中試圖定義一種 Java 內(nèi)存模型(Java Memory Model,簡稱 JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓 Java 程序在各種平臺下都能達到一致的內(nèi)存訪問效果,不必因為不同平臺上的物理機的內(nèi)存模型的差異,對各平臺定制化開發(fā)程序。
更具體一點說,Java 內(nèi)存模型提出目標在于,定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié)。
?
從上圖可以得出結(jié)論:
-
所有
的變量都存儲在主內(nèi)存
中 -
每個
線程都有自己獨立的工作內(nèi)存
,里面保存該線程使用到的變量的副本(主內(nèi)存該變量的一份拷貝)。
JMM關(guān)于synchronized的兩條規(guī)定:
- 線程解鎖前,必須把
共享變量的最新值刷新到主內(nèi)存中
- 線程加鎖時,將
清空工作內(nèi)存中共享變量的值
,從而使用
共享變量時需要從主內(nèi)存中重新讀取最新的值
。(加鎖和解鎖需要是同一把鎖)
線程解鎖前對共享變量的修改在下次加鎖前對其他線程可見。
JVM主內(nèi)存與工作內(nèi)存描述
JVM將內(nèi)存為主內(nèi)存
和工作內(nèi)存
兩個部分。
主內(nèi)存:?主要包括本地方法區(qū)
和?堆
- Java 內(nèi)存模型規(guī)定了
所有變量都存儲在主內(nèi)存(Main Memory)中
(此處的主內(nèi)存與介紹物理硬件的主內(nèi)存名字一樣,兩者可以互相類比,但此處僅是虛擬機內(nèi)存的一部分)。
工作內(nèi)存:?每個線程
都有一個工作內(nèi)存,工作內(nèi)存中主要包括兩個部分,一個是屬于該線程私有的棧
和?對主存部分變量拷貝的寄存器
(包括程序計數(shù)器PC和cup工作的高速緩存區(qū))。
- 每個線程都有自己的
工作內(nèi)存(Working Memory,又稱本地內(nèi)存.)
,線程的工作內(nèi)存中保存了該線程使用到的變量,該變量是主內(nèi)存中的共享變量的副本拷貝
。 - (工作內(nèi)存是 JMM 的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。)
?線程執(zhí)行的時候,將首先從主內(nèi)存讀值
,再load到工作內(nèi)存中的副本
中,然后傳給處理器
執(zhí)行,執(zhí)行完畢后再給工作內(nèi)存中的副本賦值
,隨后工作內(nèi)存再把值傳回給主存
,主存中的值才更新。
在這個過程中如果出現(xiàn)多個線程同時在處理這些值,豈不是會出現(xiàn)并發(fā)問題?
1、所有的變量都存儲在主內(nèi)存中(虛擬機內(nèi)存的一部分),對于所有線程都是共享的
2、每個線程都有自己的工作內(nèi)存,工作內(nèi)存中保存的是主存中某些變量的值的副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進行,不能直接讀寫主內(nèi)存中的變量。
3、線程之間無法直接訪問對方的工作內(nèi)存中的變量值的,線程間變量的傳遞均需要通過主內(nèi)存來完成。
這種劃分與Java運行時內(nèi)存區(qū)域中堆、棧、元空間等的劃分是不同層次的劃分,兩者基本沒有關(guān)系。硬要聯(lián)系的話,大致上主內(nèi)存對應(yīng)Java堆中對象的實例數(shù)據(jù)部分、工作內(nèi)存對應(yīng)棧的部分區(qū)域;從更低層次上說,主內(nèi)存對應(yīng)物理硬件內(nèi)存、工作內(nèi)存對應(yīng)寄存器和高速緩存。
JVM內(nèi)存間交互規(guī)則
關(guān)于主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存之類的實現(xiàn)細節(jié),Java 內(nèi)存模型中定義了下面 8 種操作來完成。
- Lock(鎖定):作用于主內(nèi)存中的變量,把一個變量標識為被一個線程獨占的狀態(tài)。
- Unlock(解鎖):作用于主內(nèi)存中的變量, 將一個變量從鎖定狀態(tài)(Lock)釋放出來,釋放后的變量才可以被其他線程鎖定(Lock)
-
Read(讀取)
:作用于主內(nèi)存中的變量,將一個變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存中,以便隨后的load操作使用 -
Load(加載)
:作用于工作內(nèi)存中的變量,把read操作從主內(nèi)存中得到的變量的值放入工作內(nèi)存的變量副本中。 - Use(使用):作用于工作內(nèi)存中的變量,把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎。(每當虛擬機遇到一個需要使用到變量的值的字節(jié)碼指令時就會執(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)存的變量中。
需知:
- 在將變量從主內(nèi)存讀取到工作內(nèi)存中,必須順序執(zhí)行
read(讀取)、load(加載)
; - 要將變量從工作內(nèi)存同步回主內(nèi)存中,必須順序執(zhí)行
store(存儲)、write(寫入)
。
這8種操作必須遵循以下規(guī)則:
-
不允許
read(讀取)和load(加載)、store(存儲)和write(寫入)
操作之一單獨出現(xiàn)
。即不允許一個變量從主內(nèi)存被讀取了,但是工作內(nèi)存不接受,或者從工作內(nèi)存回寫了但是主內(nèi)存不接受。不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步會主內(nèi)存中
-
不允許一個線程丟棄它最近的一個
assign(賦值)
操作,即變量在工作內(nèi)存被更改后必須同步改更改回主內(nèi)存。
即:執(zhí)行store(存儲),write(寫入)操作 -
工作內(nèi)存中的變量在沒有執(zhí)行過
assign(賦值)
操作時,不允許無意義的同步回主內(nèi)存。 即:執(zhí)行store(存儲),write(寫入)操作 -
在執(zhí)行
use(使用)
前必須已執(zhí)行load(加載)
,在執(zhí)行store(存儲)
前必須已執(zhí)行assign(賦值)
。 -
一個變量在
同一時刻
只允許一個線程對其執(zhí)行l(wèi)ock操作
,一個線程可以對同一個變量重復(fù)執(zhí)行多次lock,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖。lock和unlock必須成對出現(xiàn)。 -
一個線程在
lock
一個變量的時候,將會清空
工作內(nèi)存中的此變量的值,執(zhí)行引擎在use(使用)
前必須重新read(讀取)和load(加載)
初始化變量的值。 -
在執(zhí)行
unlock
之前,必須首先執(zhí)行了store(存儲)和write(寫入)
操作對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)
-
線程不允許
unlock
其他線程的lock
操作。并且unlock操作必須是在本線程的lock操作之后。如果一個變量事先沒有被lock操作鎖定,則不允許對它執(zhí)行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
從上面可以看出,把變量從主內(nèi)存復(fù)制到工作內(nèi)存需要順序執(zhí)行read、load
,從工作內(nèi)存同步回主內(nèi)存則需要順序執(zhí)行store、write??偨Y(jié):
- read、load、use必須成對順序出現(xiàn),但不要求連續(xù)出現(xiàn)。assign、store、write同之;
- 變量誕生和初始化:變量只能從主內(nèi)存“誕生”,且須先初始化后才能使用,即在use/store前須先load/assign;
- lock一個變量后會清空工作內(nèi)存中該變量的值,使用前須先初始化;unlock前須將變量同步回主內(nèi)存;
- 一個變量同一時刻只能被一線程lock,lock幾次就須unlock幾次;未被lock的變量不允許被執(zhí)行unlock,一個線程不能去unlock其他線程lock的變量。
JVM先行發(fā)生原則
Java內(nèi)存模型具備一些先天的“有序性”,即不需要通過任何同步手段(volatile、synchronized等)就能夠得到保證的有序性,這個通常也稱為happens-before原則。
?
知識來源:
【23版面試突擊】你知道主內(nèi)存和工作內(nèi)存的關(guān)系?_嗶哩嗶哩_bilibili文章來源:http://www.zghlxwxcb.cn/news/detail-683910.html
【Java多線程】內(nèi)存模型JMM—主內(nèi)存與工作內(nèi)存分析_主內(nèi)存和工作內(nèi)存_Archie_java的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-683910.html
到了這里,關(guān)于java八股文面試[多線程]——主內(nèi)存和工作內(nèi)存的關(guān)系的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!