Java內(nèi)存模型
Java虛擬機(jī)規(guī)范中試圖定義一種Java內(nèi)存模型(Java Memory Model,簡(jiǎn)稱(chēng)JMM)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。
定義Java內(nèi)存模型并非一件容易的事情,這個(gè)模型必須定義得足夠嚴(yán)謹(jǐn),才能讓Java的并非內(nèi)存訪問(wèn)操作不會(huì)產(chǎn)生歧義:但是,也必須定義的足夠?qū)捤?,使得虛擬機(jī)的實(shí)現(xiàn)有足夠的自由空間去利用硬件的各種特性(寄存器、高速緩存和指令集中某些特有的指令)來(lái)獲取更好的執(zhí)行速度。經(jīng)過(guò)長(zhǎng)時(shí)間的驗(yàn)證和修補(bǔ),在JDK1.5(實(shí)現(xiàn)了JSR-133)發(fā)布后,Java內(nèi)存模型已經(jīng)成熟和完善起來(lái)了。
主內(nèi)存和工作內(nèi)存
Java內(nèi)存模型的主要目標(biāo)就是定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。此處的變量(Variables)與Java編程中所說(shuō)的變量有所區(qū)別,它包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但不包括局部變量與方法參數(shù),因?yàn)楹笳呤蔷€程私有,不會(huì)被共享,自然就不會(huì)存在競(jìng)爭(zhēng)問(wèn)題。
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存(Main Memory)中,每個(gè)線程還有自己的工作內(nèi)存(Working Memory),線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量,不同線程之間也無(wú)法直接訪問(wèn)其他線程工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成。線程、主內(nèi)存、工作內(nèi)存三者的交互關(guān)系如下圖所示:

原子性、可見(jiàn)性與有序性
Java內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性、可見(jiàn)性與有序性這3個(gè)特征來(lái)建立,我們逐個(gè)來(lái)看一下哪些操作實(shí)現(xiàn)了這3個(gè)特性。
原子性
原子性是指一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。
除了long型字段和double型字段外,Java內(nèi)存模型確保訪問(wèn)任意類(lèi)型字段所對(duì)應(yīng)的內(nèi)存單元都是原子的。這包括引用其它對(duì)象的引用類(lèi)型的字段。此外,volatile long 和volatile double也具有原子性 。
如果應(yīng)用程序需要一個(gè)更大范圍的原子性保證,Java內(nèi)存模型還提供了顯示鎖和synchronized 關(guān)鍵字。
可見(jiàn)性
可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。
Java內(nèi)存模型是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存將刷新變量值這種依賴(lài)主內(nèi)存作為傳遞媒介的方式來(lái)實(shí)現(xiàn)可見(jiàn)性的。無(wú)論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區(qū)別是:volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,已經(jīng)每次使用前立即從主內(nèi)存刷新。因此,volatile關(guān)鍵字可以保證多線程操作時(shí)變量的可見(jiàn)性。
除了volatile之外,Java還有兩個(gè)關(guān)鍵字能實(shí)現(xiàn)可見(jiàn)性,即synchronized和final。
有序性
有序性是指 程序執(zhí)行的順序按照代碼中的先后順序執(zhí)行。
Java語(yǔ)言提供了volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性。
happens-before
從JDK5開(kāi)始,java使用新的JSR -133內(nèi)存模型(本文除非特別說(shuō)明,針對(duì)的都是JSR- 133內(nèi)存模型)。JSR-133使用happens-before的概念來(lái)闡述操作之間的內(nèi)存可見(jiàn)性。在JMM中,如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在happens-before關(guān)系。這里提到的兩個(gè)操作既可以是在一個(gè)線程之內(nèi),也可以是在不同線程之間。
與程序員密切相關(guān)的happens-before規(guī)則如下:
- 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens- before 于該線程中的任意后續(xù)操作。
- 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器鎖的解鎖,happens- before 于隨后對(duì)這個(gè)監(jiān)視器鎖的加鎖。
- volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě),happens- before 于任意后續(xù)對(duì)這個(gè)volatile域的讀。
- 傳遞性:如果A happens- before B,且B happens- before C,那么A happens- before C。
注意,兩個(gè)操作之間具有happens-before關(guān)系,并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行!happens-before僅僅要求前一個(gè)操作(執(zhí)行的結(jié)果)對(duì)后一個(gè)操作可見(jiàn),且前一個(gè)操作按順序排在第二個(gè)操作之前(the first is visible to and ordered before the second)。