千鋒扣丁學(xué)堂Java培訓(xùn)之基礎(chǔ)JVM內(nèi)存模型
2019-07-30 15:23:29
3803瀏覽
今天千鋒扣丁學(xué)堂
Java培訓(xùn)老師給大家分享一篇關(guān)于JVM內(nèi)存模型的詳細(xì)介紹,首先在并發(fā)編程中,我們通常要處理兩個問題:線程之前如何通信與線程之間如何同步。通信是指線程之間如何交換信息,通常的通信手段有:共享內(nèi)存與消息傳遞(語言不同,通信機(jī)制不同,java使用的是共享內(nèi)存的并發(fā)模型)
在共享內(nèi)存的并發(fā)模型中,線程之間共享信息的公共狀態(tài),通過對信息公共狀態(tài)的讀-寫來隱使地進(jìn)行線程通信;而在消息傳遞的并發(fā)模型中,由于線程直接沒有信息的公共狀態(tài),所以只能傳遞明確消息來顯式地進(jìn)行通信
同步是指控制不同線程的操作發(fā)生相對順序的機(jī)制,在共享內(nèi)存并發(fā)模型里,同步是顯式進(jìn)行的,程序員必須顯式指定某個方法或某段代碼需要在線程之間互斥執(zhí)行。在消息傳遞的并發(fā)模型里,由于消息的發(fā)送必須在消息的接收之前,因此同步是隱式進(jìn)行的。
由于java使用的是共享內(nèi)存的并發(fā)模型,線程之間的通信總是隱式進(jìn)行,整個通信過程對程序員完全透明,如果不理解隱式進(jìn)行的線程之間通信的工作機(jī)制,很可能會遇到各種奇怪的內(nèi)存可見性問題。
Java內(nèi)存模型的抽象
上面已經(jīng)給出來了Java使用的是共享內(nèi)存的并發(fā)模型,在前一篇JVM的內(nèi)存結(jié)構(gòu)中,只有兩塊內(nèi)存是被線程之間共享的,堆和方法區(qū),所以這兩塊區(qū)域也是作為共享內(nèi)存的信息存儲區(qū),包括實例域、靜態(tài)域和數(shù)組元素(本文使用“共享變量”這個術(shù)語代指實例域,靜態(tài)域和數(shù)組元素)
Java線程之間的通信由Java內(nèi)存模型(本文簡稱為JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(mainmemory)中,每個線程都有一個私有的本地內(nèi)存(localmemory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。Java內(nèi)存模型的抽象示意圖如下:
線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟(假設(shè)共享變量為X):1.線程A先更新本地內(nèi)存X,然后把更新過的共享變量刷到主內(nèi)存中去。
2.線程B到主內(nèi)存中去讀取線程A已更新過的共享變量X,同時更新本地內(nèi)存中的共享變量(若已存在該共享變量)。
這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序員提供內(nèi)存可見性保證。
重排序
在執(zhí)行程序時為了提高性能,編譯器和處理器常常會對指令做重排序。重排序分三種類型:
1.編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。
2.指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-LevelParallelism,ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序。
3.內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。
從java源代碼到最終實際執(zhí)行的指令序列,會分別經(jīng)歷下面三種重排序:
上述的1屬于編譯器重排序,2和3屬于處理器重排序。這些重排序都可能會導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題。對于編譯器,JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對于處理器重排序,JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障(memorybarriers,intel稱之為memoryfence)指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。
說了這么多,舉個例子:
int a = 1; // step 1
boolean b = true; // step 2
正常情況下,step2不依賴step1的執(zhí)行,所以step1跟step2會發(fā)生重排序。但是如果后面有這么一段代碼:
if (b) {
int c = a; //step 3
System.out.println(c);
}
假設(shè)一種情況,在編譯器重排序下,線程1中,如果step2在step1之前執(zhí)行,線程2走到了step3,并且step1還沒有把a(bǔ)的值1刷新到主內(nèi)存,此時C的值并不會是1,這就是編譯器重排序引來的問題,前面也講到處理器重排序插入特定類型的內(nèi)存屏障,這就是來解決編譯器重排序帶來的問題。
內(nèi)存屏障
現(xiàn)代的處理器使用寫緩沖區(qū)來臨時保存向內(nèi)存寫入的數(shù)據(jù)。寫緩沖區(qū)可以保證指令流水線持續(xù)運(yùn)行,它可以避免由于處理器停頓下來等待向內(nèi)存寫入數(shù)據(jù)而產(chǎn)生的延遲。但是對內(nèi)存的讀/寫操作的執(zhí)行順序,不一定與內(nèi)存實際發(fā)生的讀/寫操作順序一致!
為了保證內(nèi)存可見性,java編譯器在生成指令序列的適當(dāng)位置會插入內(nèi)存屏障指令來禁止特定類型的處理器重排序。JMM把內(nèi)存屏障指令分為下列四類:
StoreLoadBarriers是一個“全能型”的屏障,它同時具有其他三個屏障的效果?,F(xiàn)代的多處理器大都支持該屏障(其他類型的屏障不一定被所有處理器支持)。執(zhí)行該屏障開銷會很昂貴,因為當(dāng)前處理器通常要把寫緩沖區(qū)中的數(shù)據(jù)全部刷新到內(nèi)存中(bufferfullyflush)。同時這個屏障也解決了上面的問題,給c賦值前必須要先去a,那么在取a之前插入LoadStore,就可以保證讀取到最新的a值了!
以上就是關(guān)于千鋒扣丁學(xué)堂Java培訓(xùn)之基礎(chǔ)JVM內(nèi)存模型的全部內(nèi)容,
想要了解更多關(guān)于Java開發(fā)方面內(nèi)容的小伙伴,請關(guān)注扣丁學(xué)堂Java培訓(xùn)官網(wǎng)、微信等平臺,扣丁學(xué)堂IT職業(yè)在線學(xué)習(xí)教育有專業(yè)的Java講師為您指導(dǎo),此外扣丁學(xué)堂老師精心推出的Java視頻教程定能讓你快速掌握J(rèn)ava從入門到精通開發(fā)實戰(zhàn)技能??鄱W(xué)堂Java技術(shù)交流群:850353792。
【關(guān)注微信公眾號獲取更多學(xué)習(xí)資料】 【掃碼進(jìn)入JavaEE/微服務(wù)VIP免費(fèi)公開課】
查看更多關(guān)于“Java開發(fā)資訊”的相關(guān)文章>>
標(biāo)簽:
Java培訓(xùn)
Java視頻教程
Java多線程
Java面試題
Java學(xué)習(xí)視頻
springBoot項目