深入淺出Java內(nèi)存模型

面試官:我記得上一次已經(jīng)問過了為什么要有Java內(nèi)存模型
面試官:我記得你的最終答案是:Java為了屏蔽硬件和操作系統(tǒng)訪問內(nèi)存的各種差異,提出了「Java內(nèi)存模型」的規(guī)范,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問都能得到一致效果
候選者:嗯,對(duì)的
面試官:要不,你今天再來講講Java內(nèi)存模型這里邊的內(nèi)容唄?
候選者:嗯,在講之前還是得強(qiáng)調(diào)下:Java內(nèi)存模型它是一種「規(guī)范」,Java虛擬機(jī)會(huì)實(shí)現(xiàn)這個(gè)規(guī)范。
候選者:Java內(nèi)存模型主要的內(nèi)容,我個(gè)人覺得有以下幾塊吧
候選者:1. Java內(nèi)存模型的抽象結(jié)構(gòu)
候選者:2. happen-before規(guī)則
候選者:3.對(duì)volatile內(nèi)存語義的探討(這塊我后面再好好解釋)

面試官:那要不你就從第一點(diǎn)開始唄?先聊下Java內(nèi)存模型的抽象結(jié)構(gòu)?
候選者:嗯。Java內(nèi)存模型定義了:Java線程對(duì)內(nèi)存數(shù)據(jù)進(jìn)行交互的規(guī)范。
候選者:線程之間的「共享變量」存儲(chǔ)在「主內(nèi)存」中,每個(gè)線程都有自己私有的「本地內(nèi)存」,「本地內(nèi)存」存儲(chǔ)了該線程以讀/寫共享變量的副本。
候選者:本地內(nèi)存是Java內(nèi)存模型的抽象概念,并不是真實(shí)存在的。
候選者:順便畫個(gè)圖吧,看完圖就懂了。

候選者:Java內(nèi)存模型規(guī)定了:線程對(duì)變量的所有操作都必須在「本地內(nèi)存」進(jìn)行,「不能直接讀寫主內(nèi)存」的變量
候選者:Java內(nèi)存模型定義了8種操作來完成「變量如何從主內(nèi)存到本地內(nèi)存,以及變量如何從本地內(nèi)存到主內(nèi)存」
候選者:分別是read/load/use/assign/store/write/lock/unlock操作
候選者:看著8個(gè)操作很多,對(duì)變量的一次讀寫就涵蓋了這些操作了,我再畫個(gè)圖給你講講

候選者:懂了吧?無非就是讀寫用到了各個(gè)操作(:
面試官:懂了,很簡(jiǎn)單,接下來說什么是happen-before吧?
候選者:嗯,好的(:
候選者:按我的理解下,happen-before實(shí)際上也是一套「規(guī)則」。Java內(nèi)存模型定義了這套規(guī)則,目的是為了闡述「操作之間」的內(nèi)存「可見性」
候選者:從上次講述「指令重排」就提到了,在CPU和編譯器層面上都有指令重排的問題。
候選者:指令重排雖然是能提高運(yùn)行的效率,但在并發(fā)編程中,我們?cè)诩骖櫋感省沟那疤嵯?,還希望「程序結(jié)果」能由我們掌控的。
候選者:說白了就是:在某些重要的場(chǎng)景下,這一組操作都不能進(jìn)行重排序,「前面一個(gè)操作的結(jié)果對(duì)后續(xù)操作必須是可見的」。

面試官:嗯…
候選者:于是,Java內(nèi)存模型就提出了happen-before這套規(guī)則,規(guī)則總共有8條
候選者:比如傳遞性、volatile變量規(guī)則、程序順序規(guī)則、監(jiān)視器鎖的規(guī)則…(具體看規(guī)則的含義就好了,這塊不難)
候選者:只要記住,有了happen-before這些規(guī)則。我們寫的代碼只要在這些規(guī)則下,前一個(gè)操作的結(jié)果對(duì)后續(xù)操作是可見的,是不會(huì)發(fā)生重排序的。
面試官:我明白你的意思了
面試官:那最后說下volatile?
候選者:嗯,volatile是Java的一個(gè)關(guān)鍵字
候選者:為什么講Java內(nèi)存模型往往就會(huì)講到volatile這個(gè)關(guān)鍵字呢,我覺得主要是它的特性:可見性和有序性(禁止重排序)
候選者:Java內(nèi)存模型這個(gè)規(guī)范,很大程度下就是為了解決可見性和有序性的問題。

面試官:那你來講講它的原理吧,volatile這個(gè)關(guān)鍵字是怎么做到可見性和有序性的
候選者:Java內(nèi)存模型為了實(shí)現(xiàn)volatile有序性和可見性,定義了4種內(nèi)存屏障的「規(guī)范」,分別是LoadLoad/LoadStore/StoreLoad/StoreStore
候選者:回到volatile上,說白了,就是在volatile「前后」加上「內(nèi)存屏障」,使得編譯器和CPU無法進(jìn)行重排序,致使有序,并且寫volatile變量對(duì)其他線程可見。
候選者:Java內(nèi)存模型定義了規(guī)范,那Java虛擬機(jī)就得實(shí)現(xiàn)啊,是不是?
面試官:嗯…
候選者:之前看過Hotspot虛擬機(jī)的實(shí)現(xiàn),在「匯編」層面上實(shí)際是通過Lock前綴指令來實(shí)現(xiàn)的,而不是各種fence指令(主要原因就是簡(jiǎn)便。因?yàn)榇蟛糠制脚_(tái)都支持lock指令,而fence指令是x86平臺(tái)的)。
候選者:lock指令能保證:禁止CPU和編譯器的重排序(保證了有序性)、保證CPU寫核心的指令可以立即生效且其他核心的緩存數(shù)據(jù)失效(保證了可見性)。

面試官:那你提到這了,我想問問volatile和MESI協(xié)議是啥關(guān)系?
候選者:它們沒有直接的關(guān)聯(lián)。
候選者:Java內(nèi)存模型關(guān)注的是編程語言層面上,它是高維度的抽象。MESI是CPU緩存一致性協(xié)議,不同的CPU架構(gòu)都不一樣,可能有的CPU壓根就沒用MESI協(xié)議…
候選者:只不過MESI名聲大,大家就都拿他來舉例子了。而MESI可能只是在「特定的場(chǎng)景下」為實(shí)現(xiàn)volatile的可見性/有序性而使用到的一部分罷了(:
面試官:嗯…
候選者:為了讓Java程序員屏蔽上面這些底層知識(shí),快速地入門使用volatile變量
候選者:Java內(nèi)存模型的happen-before規(guī)則中就有對(duì)volatile變量規(guī)則的定義
候選者:這條規(guī)則的內(nèi)容其實(shí)就是:對(duì)一個(gè) volatile 變量的寫操作相對(duì)于后續(xù)對(duì)這個(gè) volatile 變量的讀操作可見
候選者:它通過happen-before規(guī)則來規(guī)定:只要變量聲明了volatile 關(guān)鍵字,寫后再讀,讀必須可見寫的值。(可見性、有序性)
面試官:嗯…了解了
本文總結(jié):
- 為什么存在Java內(nèi)存模型:Java為了屏蔽硬件和操作系統(tǒng)訪問內(nèi)存的各種差異,提出了「Java內(nèi)存模型」的規(guī)范,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問都能得到一致效果
- Java內(nèi)存模型抽象結(jié)構(gòu):線程之間的「共享變量」存儲(chǔ)在「主內(nèi)存」中,每個(gè)線程都有自己私有的「本地內(nèi)存」,「本地內(nèi)存」存儲(chǔ)了該線程以讀/寫共享變量的副本。線程對(duì)變量的所有操作都必須在「本地內(nèi)存」進(jìn)行,而「不能直接讀寫主內(nèi)存」的變量
- happen-before規(guī)則:Java內(nèi)存模型規(guī)定在某些場(chǎng)景下(一共8條),前面一個(gè)操作的結(jié)果對(duì)后續(xù)操作必須是可見的。這8條規(guī)則成為happen-before規(guī)則
- volatile:volatile是Java的關(guān)鍵字,修飾的變量是可見性且有序的(不會(huì)被重排序)??梢娦杂蒱appen-before規(guī)則完成,有序性由Java內(nèi)存模型定義的「內(nèi)存屏障」完成,實(shí)際HotSpot虛擬機(jī)實(shí)現(xiàn)Java內(nèi)存模型規(guī)范,匯編底層通過Lock指令來實(shí)現(xiàn)。

對(duì)線面試官PDF版本,可+V: java3yyy 免費(fèi)領(lǐng)取