java內(nèi)存模型

簡(jiǎn)書(shū) 占小狼
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!

java并發(fā)采用的是共享內(nèi)存模型,線程之間的通信對(duì)程序員來(lái)說(shuō)是透明的,內(nèi)存可見(jiàn)性問(wèn)題很容易困擾著java程序員,今天我們就來(lái)揭開(kāi)java內(nèi)存模型的神秘面紗。


在揭開(kāi)面紗之前,我們需要認(rèn)識(shí)幾個(gè)基礎(chǔ)概念:內(nèi)存屏障(memory Barriers),指令重排序,happens-before規(guī)則,as-if-serial語(yǔ)義。

什么是 Memory Barrier(內(nèi)存屏障)?

內(nèi)存屏障,又稱內(nèi)存柵欄,是一個(gè)CPU指令,基本上它是一條這樣的指令:
1、保證特定操作的執(zhí)行順序。
2、影響某些數(shù)據(jù)(或則是某條指令的執(zhí)行結(jié)果)的內(nèi)存可見(jiàn)性。

編譯器和CPU能夠重排序指令,保證最終相同的結(jié)果,嘗試優(yōu)化性能。插入一條Memory Barrier會(huì)告訴編譯器和CPU:不管什么指令都不能和這條Memory Barrier指令重排序。

Memory Barrier所做的另外一件事是強(qiáng)制刷出各種CPU cache,如一個(gè) Write-Barrier(寫(xiě)入屏障)將刷出所有在 Barrier 之前寫(xiě)入 cache 的數(shù)據(jù),因此,任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本。


Memory Barrier.png

這和java有什么關(guān)系?volatile是基于Memory Barrier實(shí)現(xiàn)的。

如果一個(gè)變量是volatile修飾的,JMM會(huì)在寫(xiě)入這個(gè)字段之后插進(jìn)一個(gè)Write-Barrier指令,并在讀這個(gè)字段之前插入一個(gè)Read-Barrier指令。


volatile.png

這意味著,如果寫(xiě)入一個(gè)volatile變量a,可以保證:
1、一個(gè)線程寫(xiě)入變量a后,任何線程訪問(wèn)該變量都會(huì)拿到最新值。
2、在寫(xiě)入變量a之前的寫(xiě)入操作,其更新的數(shù)據(jù)對(duì)于其他線程也是可見(jiàn)的。因?yàn)镸emory Barrier會(huì)刷出cache中的所有先前的寫(xiě)入。

happens-before

從jdk5開(kāi)始,java使用新的JSR-133內(nèi)存模型,基于happens-before的概念來(lái)闡述操作之間的內(nèi)存可見(jiàn)性。

在JMM中,如果一個(gè)操作的執(zhí)行結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在happens-before關(guān)系,這個(gè)的兩個(gè)操作既可以在同一個(gè)線程,也可以在不同的兩個(gè)線程中。

與程序員密切相關(guān)的happens-before規(guī)則如下:
1、程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中任意的后續(xù)操作。
2、監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖操作,happens-before于隨后對(duì)這個(gè)鎖的加鎖操作。
3、volatile域規(guī)則:對(duì)一個(gè)volatile域的寫(xiě)操作,happens-before于任意線程后續(xù)對(duì)這個(gè)volatile域的讀。
4、傳遞性規(guī)則:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

注意:兩個(gè)操作之間具有happens-before關(guān)系,并不意味前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行!僅僅要求前一個(gè)操作的執(zhí)行結(jié)果,對(duì)于后一個(gè)操作是可見(jiàn)的,且前一個(gè)操作按順序排在后一個(gè)操作之前。

指令重排序

在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器會(huì)對(duì)指令做重排序。但是,JMM確保在不同的編譯器和不同的處理器平臺(tái)之上,通過(guò)插入特定類型的Memory Barrier來(lái)禁止特定類型的編譯器重排序和處理器重排序,為上層提供一致的內(nèi)存可見(jiàn)性保證。

1、編譯器優(yōu)化重排序:編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序。
2、指令級(jí)并行的重排序:如果不存l在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。
3、內(nèi)存系統(tǒng)的重排序:處理器使用緩存和讀寫(xiě)緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

數(shù)據(jù)依賴性

如果兩個(gè)操作訪問(wèn)同一個(gè)變量,其中一個(gè)為寫(xiě)操作,此時(shí)這兩個(gè)操作之間存在數(shù)據(jù)依賴性。
編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴性關(guān)系的兩個(gè)操作的執(zhí)行順序,即不會(huì)重排序。

as-if-serial

不管怎么重排序,單線程下的執(zhí)行結(jié)果不能被改變,編譯器、runtime和處理器都必須遵守as-if-serial語(yǔ)義。

抽象結(jié)構(gòu)

java線程之間的通信由java內(nèi)存模型(JMM)控制,JMM決定一個(gè)線程對(duì)共享變量(實(shí)例域、靜態(tài)域和數(shù)組)的寫(xiě)入何時(shí)對(duì)其它線程可見(jiàn)。

從抽象的角度來(lái)看,JMM定義了線程和主內(nèi)存Main Memory(堆內(nèi)存)之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存中,每個(gè)線程都有自己的本地內(nèi)存Local Memory(只是一個(gè)抽象概念,物理上不存在),存儲(chǔ)了該線程的共享變量副本。

所以,線程A和線程B之前需要通信的話,必須經(jīng)過(guò)一下兩個(gè)步驟:
1、線程A把本地內(nèi)存中更新過(guò)的共享變量刷新到主內(nèi)存中。
2、線程B到主內(nèi)存中讀取線程A之前更新過(guò)的共享變量。

END。
我是占小狼。
在魔都艱苦奮斗,白天是上班族,晚上是知識(shí)服務(wù)工作者。
讀完我的文章有收獲,記得關(guān)注和點(diǎn)贊哦,如果非要打賞,我也是不會(huì)拒絕的啦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容