java 并发模型总类_java并发编程系列-内存模型基础
java線程之間的通信對(duì)程序開(kāi)發(fā)人員是完全透明的,內(nèi)存的可見(jiàn)性問(wèn)題很容易困擾很多開(kāi)發(fā)人員。本篇博文將揭開(kāi)java內(nèi)存模型的神秘面紗,來(lái)看看內(nèi)存模型到底是怎樣的。
并發(fā)編程模型的分類
并發(fā)編程中需要處理的兩個(gè)關(guān)鍵問(wèn)題:線程之間如何通信
線程之間如何同步
所謂通信是指線程之間以何種機(jī)制來(lái)交換信息,在命令式編程中,線程的通信機(jī)制有兩種:共享內(nèi)存(隱式通信:通過(guò)共享程序的公共狀態(tài),讀-寫內(nèi)存中的公共狀態(tài)實(shí)現(xiàn))
消息傳遞(顯示通信:線程間發(fā)送消息實(shí)現(xiàn) ,比較典型的就是wait()和notify())
所謂同步,就是控制不同線程間操作發(fā)生相對(duì)順序的機(jī)制:共享內(nèi)存(同步是顯示的,由程序開(kāi)發(fā)人員顯示的指定某段代碼或者某個(gè)方法需要在線程之間互斥執(zhí)行)。
消息傳遞(同步是隱式的,消息的發(fā)送必須在消息接收之前)。
java的并發(fā)采用的是共享內(nèi)存模型,線程之間的通信是隱式執(zhí)行的,同步需要開(kāi)發(fā)人員顯示進(jìn)行控制。
JAVA內(nèi)存模型(JMM)的抽象
JMM把java虛擬機(jī)內(nèi)部劃分為線程棧和堆。邏輯視圖如下:
JMM邏輯視圖.png
java中所有的實(shí)例域、靜態(tài)域,數(shù)組元素都是存儲(chǔ)在堆內(nèi)存,堆內(nèi)存在線程之間共享。而對(duì)象引用,局部變量、方法參數(shù)和異常處理器參數(shù)都是存在在棧內(nèi)存,也就是線程棧中,線程棧中的變量?jī)H對(duì)自己可見(jiàn),對(duì)其他線程不可見(jiàn)。不同線程之間的通信由java內(nèi)存模型(java memory model ,簡(jiǎn)稱JMM)控制。JMM的抽象結(jié)構(gòu)圖,如下:
JMM內(nèi)存模型抽象圖.png
線程之間的共享變量存儲(chǔ)在堆內(nèi)存,每個(gè)線程都有私有的本地內(nèi)存(線程棧),私有本地內(nèi)存中存儲(chǔ)了主內(nèi)存中共享變量的拷貝,本地內(nèi)存只是JMM的一個(gè)抽象概念,并不真實(shí)存在。
上圖中線程A要與線程B通信的話,由于線程本地變量的不可見(jiàn)性,首先要將線程A中變量的更改,刷新到主內(nèi)存中,然后線程B本地私有的共享變量副本失效,從新讀取刷新的新值,才能完成。從上面的描述看,線程A向線程B通信,必須要經(jīng)過(guò)主內(nèi)存,JMM控制主內(nèi)存與每個(gè)線程的本地變量的交互,來(lái)為java程序員提供內(nèi)存的可見(jiàn)性。
硬件內(nèi)存架構(gòu)
軟件最終還要運(yùn)行在硬件上,看一下現(xiàn)代計(jì)算機(jī)硬件內(nèi)存架構(gòu)的簡(jiǎn)單圖示:
硬件模型.png
現(xiàn)在的計(jì)算機(jī)一般都有兩個(gè)或者多個(gè)CPU,其中有些還是多核心實(shí)現(xiàn)。
每個(gè)CPU都包含一系列的寄存器,它們是CPU內(nèi)內(nèi)存的基礎(chǔ)。CPU在寄存器上執(zhí)行操作的速度遠(yuǎn)大于在主存上執(zhí)行的速度。這是因?yàn)镃PU訪問(wèn)寄存器的速度遠(yuǎn)大于主存。
每個(gè)CPU可能還有一個(gè)CPU緩存層。實(shí)際上,絕大多數(shù)的現(xiàn)代CPU都有一定大小的緩存層。CPU訪問(wèn)緩存層的速度快于訪問(wèn)主存的速度,但通常比訪問(wèn)內(nèi)部寄存器的速度還要慢一點(diǎn)。一些CPU還有多層緩存,但這些對(duì)理解Java內(nèi)存模型如何和內(nèi)存交互不是那么重要。只要知道CPU中可以有一個(gè)緩存層就可以了。
一個(gè)計(jì)算機(jī)還包含一個(gè)主存。所有的CPU都可以訪問(wèn)主存。主存通常比CPU中的緩存大得多。
CPU的高速緩存雖然解決了效率的問(wèn)題,但是又帶來(lái)了一個(gè)新的問(wèn)題:數(shù)據(jù)一致性。當(dāng)一個(gè)CPU需要讀取主存時(shí),它會(huì)將主存的部分讀到CPU緩存中。它甚至可能將緩存中的部分內(nèi)容讀到它的內(nèi)部寄存器中,然后在寄存器中執(zhí)行操作,這樣就不會(huì)使CPU直接與內(nèi)存相連。當(dāng)CPU需要將結(jié)果寫回到主存中去時(shí),它會(huì)將內(nèi)部寄存器的值刷新到緩存中,然后在某個(gè)時(shí)間點(diǎn)將值刷新回主存。
Java內(nèi)存模型和硬件內(nèi)存架構(gòu)之間的橋接
上面已經(jīng)提到,Java內(nèi)存模型與硬件內(nèi)存架構(gòu)之間存在差異。硬件內(nèi)存架構(gòu)沒(méi)有區(qū)分線程棧和堆。對(duì)于硬件,所有的線程棧和堆都分布在主內(nèi)中。部分線程棧和堆可能有時(shí)候會(huì)出現(xiàn)在CPU緩存中和CPU內(nèi)部的寄存器中。如下圖所示:
image.png
Java內(nèi)存模型的基礎(chǔ)原理
從源代碼到指令序列的重排序
在程序執(zhí)行時(shí),為了提高程序的執(zhí)行性能,編譯器和處理器常常會(huì)對(duì)指令做重排序,換句話說(shuō)程序的執(zhí)行順序和程序開(kāi)發(fā)人員編寫的順序可能會(huì)存在差異,這是編譯器和處理器對(duì)源代碼做了優(yōu)化。但是JMM的編譯器重排序規(guī)則會(huì)禁止特定類型的編譯器重排序,對(duì)于處理器的重排序,JMM的處理器重排序規(guī)則會(huì)要求java編譯器在生成指令時(shí),插入特定的內(nèi)存屏障(Memory Barriers)指令,來(lái)禁止特定類型的處理器重排序。換句話說(shuō)編譯器和處理器的重排序都是可控的。
重排序分為三類:編譯器重排序:編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序。
指令級(jí)重排序:現(xiàn)在的處理器都采用了并行技術(shù),可以將多條執(zhí)行重疊執(zhí)行,如果不存在數(shù)據(jù)依賴性,可以改變語(yǔ)句對(duì)應(yīng)機(jī)器語(yǔ)句的執(zhí)行順序。之所以存在數(shù)據(jù)依賴的語(yǔ)句不做重排序是因?yàn)楦淖冺樞蚝髮?dǎo)致執(zhí)行結(jié)果發(fā)生變化。
內(nèi)存系統(tǒng)重排序:由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。
JMM屬于語(yǔ)言級(jí)的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺(tái)上,通過(guò)禁止特定類型的編譯器和處理器重排序,為程序員提供一致性的內(nèi)存可見(jiàn)性保證。
重排序與內(nèi)存屏障
為了保證內(nèi)存可見(jiàn)性,Java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來(lái)禁止特定類型的處理器重排序。內(nèi)存屏障又稱為內(nèi)存柵欄,是一個(gè)CPU指令:保證特定的操作順序
影響某些數(shù)據(jù)的內(nèi)存可見(jiàn)性
例如: volatile關(guān)鍵字 就是通過(guò)內(nèi)存屏障實(shí)現(xiàn)的。
happens-before
JSP-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è)操作具有happens-before關(guān)系,并不意味著前一個(gè)操作必須要在后一個(gè)操作之間執(zhí)行,happens-before僅僅要求前一個(gè)操作的執(zhí)行結(jié)果對(duì)后一個(gè)操作可見(jiàn)。且前一個(gè)操作按順序排在第二個(gè)操作的前面。
happens-before規(guī)則如下:程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于線程中任意后續(xù)操作
監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖
volatil變量規(guī)則:對(duì)一個(gè)volatile域的寫,happens-before與任意后續(xù)對(duì)這個(gè)volatile域的讀
傳遞性:如果A happens-before B ,且B happens-before C , 那么A happens-before C .
重排序
數(shù)據(jù)依賴
如果兩個(gè)操作訪問(wèn)同一個(gè)變量,且這兩個(gè)操作中有一個(gè)為寫操作,此時(shí)這兩個(gè)操作之間就存在數(shù)據(jù)的依賴性。寫后讀 a=1;b=a
寫后寫 a=1 ; a =2
讀后寫 a=b ; b =1
上面三種情況,只要重排序兩個(gè)操作的執(zhí)行順序,程序的執(zhí)行結(jié)果就會(huì)發(fā)生改變。編譯器和處理器可能會(huì)對(duì)操作做重排序。做重排序時(shí),會(huì)遵守?cái)?shù)據(jù)依賴性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴關(guān)系的兩個(gè)操作的執(zhí)行順序,只針對(duì)單個(gè)處理器和單個(gè)線程而言。
as-if-serial語(yǔ)義
它的意思是不管怎么重排序,單線程執(zhí)行的結(jié)果都不會(huì)發(fā)生變化。為了遵守as-if-serial語(yǔ)義,編譯器和處理器都不會(huì)對(duì)存在數(shù)據(jù)依賴的語(yǔ)句執(zhí)行重排序。
順序一致性
數(shù)據(jù)競(jìng)爭(zhēng)與順序一致性保證
當(dāng)程序未正確同步時(shí),就可能存在數(shù)據(jù)競(jìng)爭(zhēng)。在一個(gè)線程中寫一個(gè)變量
在另一個(gè)線程中讀同一個(gè)變量
而且讀寫沒(méi)有通過(guò)同步來(lái)排序
順序一致性內(nèi)存模型
兩大特性:一個(gè)線程中所有操作必須按照程序的順序來(lái)執(zhí)行
所有線程都只能看到一個(gè)單一的操作執(zhí)行順序。在順序一致性內(nèi)存模型中,每個(gè)操作都必須原子執(zhí)行且立刻對(duì)所有線程可見(jiàn)。
同步程序的順序一致性效果
同步程序的順序一致性效果將于程序在順序一致性模型中的執(zhí)行結(jié)果相同。
未同步程序的執(zhí)行特性
對(duì)于未同步或未正確同步的多線程程序,JMM只提供最小安全性。JMM不保證未同步的程序的執(zhí)行結(jié)果與該程序在順序一致性模型中的執(zhí)行結(jié)果一致。
作者:起個(gè)名忒難
鏈接:https://www.jianshu.com/p/de47a2e49e5d
總結(jié)
以上是生活随笔為你收集整理的java 并发模型总类_java并发编程系列-内存模型基础的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: (JAVA)装饰流
- 下一篇: java mvc引擎_SpringMvc