淺談volatile實(shí)現(xiàn)原理

java中的volatile有兩個(gè)語義:

    1. 保證共享變量可見性
      通俗來說就是蔫缸,某個(gè)線程對(duì)一個(gè)volatile變量的修改,對(duì)于其它線程來說是可見的际起,即線程每次獲取volatile變量的值都是最新的拾碌。
    1. 禁止指令重排序

1.java內(nèi)存模型

image.png
  • 線程之間的共享變量存儲(chǔ)在主內(nèi)存中,所謂的共享變量街望,是指所有存儲(chǔ)在堆內(nèi)存上的實(shí)例域校翔、靜態(tài)域和數(shù)組元素存儲(chǔ)在堆內(nèi)存中。
  • 每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory)灾前,本地內(nèi)存中存儲(chǔ)了該線程讀/寫共享變量的副本防症。本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在哎甲。它涵蓋了緩存蔫敲,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化炭玫。

2.重排序

在執(zhí)行程序時(shí)為了提高性能奈嘿,編譯器和處理器常常會(huì)對(duì)指令做重排序。重排序有以下三種類型:

  • 編譯器優(yōu)化的重排序吞加。編譯器在不改變單線程程序語義的前提下裙犹,可以重新安排語句的執(zhí)行順序酝惧。下面代碼中a、b的賦值順序伯诬,被編譯之后可能就變成了先設(shè)置b晚唇,再設(shè)置a。
public void set() {
        a = 1;
        b = 1;
}
  • 指令級(jí)并行的重排序〉了疲現(xiàn)代處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism哩陕, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性赫舒,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序悍及。
  • 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū)接癌,這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行心赶。
    1、CPU執(zhí)行l(wèi)oad讀數(shù)據(jù)時(shí)缺猛,把讀請(qǐng)求放到LoadBuffer缨叫,這樣就不用等待其它CPU響應(yīng),先進(jìn)行下面操作荔燎,稍后再處理這個(gè)讀請(qǐng)求的結(jié)果耻姥。
    2、CPU執(zhí)行store寫數(shù)據(jù)時(shí)有咨,把數(shù)據(jù)寫到StoreBuffer中琐簇,待到某個(gè)適合的時(shí)間點(diǎn),把StoreBuffer的數(shù)據(jù)刷到主存中座享。
    由于StoreBuffer和LoadBuffer是異步執(zhí)行的婉商,所以在外面看來,先寫后讀渣叛,還是先讀后寫丈秩,沒有嚴(yán)格的固定順序。
    從java源代碼到最終實(shí)際執(zhí)行的指令序列诗箍,會(huì)分別經(jīng)歷下面三種重排序:


    image.png

上述的1屬于編譯器重排序癣籽,2和3屬于處理器重排序挽唉。這些重排序都可能會(huì)導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題滤祖。

下面舉例說明處理器重排序帶來的可見性問題

image.png

假設(shè)處理器A和處理器B按程序的順序并行執(zhí)行內(nèi)存訪問,最終卻可能得到x = y = 0的結(jié)果瓶籽。具體的原因如下圖所示:

image.png

這里處理器A和處理器B可以同時(shí)把共享變量寫入自己的寫緩沖區(qū)(A1匠童,B1),然后從內(nèi)存中讀取另一個(gè)共享變量(A2塑顺,B2)汤求,最后才把自己寫緩存區(qū)中保存的臟數(shù)據(jù)刷新到內(nèi)存中(A3俏险,B3)。當(dāng)以這種時(shí)序執(zhí)行時(shí)扬绪,程序就可以得到x = y = 0的結(jié)果竖独。

從內(nèi)存操作實(shí)際發(fā)生的順序來看,直到處理器A執(zhí)行A3來刷新自己的寫緩存區(qū)挤牛,寫操作A1才算真正執(zhí)行了莹痢。雖然處理器A執(zhí)行內(nèi)存操作的順序?yàn)椋篈1->A2,但內(nèi)存操作實(shí)際發(fā)生的順序卻是:A2->A1墓赴。此時(shí)竞膳,處理器A的內(nèi)存操作順序被重排序了(處理器B的情況和處理器A一樣,這里就不贅述了)诫硕。

這里的關(guān)鍵是坦辟,由于寫緩沖區(qū)僅對(duì)自己的處理器可見,它會(huì)導(dǎo)致處理器執(zhí)行內(nèi)存操作的順序可能會(huì)與內(nèi)存實(shí)際的操作執(zhí)行順序不一致章办。由于現(xiàn)代的處理器都會(huì)使用寫緩沖區(qū)锉走,因此現(xiàn)代的處理器都會(huì)允許對(duì)寫-讀操做重排序。

那么JMM是如何禁止重排序從而保證可見性的呢藕届?

  • 對(duì)于編譯器挠日,JMM的編譯器重排序規(guī)則會(huì)禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。
  • 對(duì)于處理器重排序翰舌,JMM的處理器重排序規(guī)則會(huì)要求java編譯器在生成指令序列時(shí)嚣潜,插入特定類型的內(nèi)存屏障(memory barriers,intel稱之為memory fence)指令椅贱,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)懂算。

3.內(nèi)存屏障

跟據(jù)上面的描述,為了保證內(nèi)存可見性庇麦,java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來禁止特定類型的處理器重排序计技。內(nèi)存屏障是一組處理指令,用來實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制山橄。JMM把內(nèi)存屏障指令分為下列四類:

image.png

4. happens-before

從JDK5開始垮媒,java使用新的JSR -133內(nèi)存模型(本文除非特別說明,針對(duì)的都是JSR- 133內(nèi)存模型)航棱。JSR-133提出了happens-before的概念睡雇,通過這個(gè)概念來闡述操作之間的內(nèi)存可見性。如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見饮醇,那么這兩個(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域的寫歌径,happens- before 于任意后續(xù)對(duì)這個(gè)volatile域的讀。
  • 傳遞性:如果A happens- before B亲茅,且B happens- before C沮脖,那么A happens- before C。

happens-before與JMM的關(guān)系如下圖所示:


image.png

如上圖所示芯急,一個(gè)happens-before規(guī)則通常對(duì)應(yīng)于多個(gè)編譯器重排序規(guī)則和處理器重排序規(guī)則勺届。對(duì)于java程序員來說,happens-before規(guī)則簡單易懂娶耍,它避免程序員為了理解JMM提供的內(nèi)存可見性保證而去學(xué)習(xí)復(fù)雜的重排序規(guī)則以及這些規(guī)則的具體實(shí)現(xiàn)免姿。

5.volatile的禁止重排序與保證可見性

  • 編譯器對(duì)操作volatile變量的代碼不再進(jìn)行優(yōu)化
  • volatile底層通過內(nèi)存屏障實(shí)現(xiàn)禁止特定類型的處理器重排序

為了實(shí)施volatile對(duì)應(yīng)的happens-before規(guī)則蘑险,對(duì)volatile變量的內(nèi)存操作涉及到的內(nèi)存屏障如下:

image.png

以volatile store -> volatile load為例讼庇,通過插入StroreLoad內(nèi)存屏障荷荤,確保Store數(shù)據(jù)對(duì)其他處理器變得可見(指刷新到內(nèi)存)单山,之前于Load2及所有后續(xù)裝載指令的裝載。從而實(shí)現(xiàn)黔牵,某個(gè)線程對(duì)volatile變量的修改會(huì)立即刷新到主內(nèi)存米者,并導(dǎo)致其它線程工作內(nèi)存中的副本無效德召,讀取時(shí)只能從主內(nèi)存加載最新的值辑舷。

參考鏈接:
1.http://www.infoq.com/cn/articles/java-memory-model-1
2.http://www.importnew.com/23520.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喻犁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子何缓,更是在濱河造成了極大的恐慌肢础,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碌廓,死亡現(xiàn)場離奇詭異传轰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谷婆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門慨蛙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纪挎,你說我怎么就攤上這事期贫。” “怎么了廷区?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵唯灵,是天一觀的道長。 經(jīng)常有香客問我隙轻,道長埠帕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任玖绿,我火速辦了婚禮敛瓷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斑匪。我一直安慰自己呐籽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布蚀瘸。 她就那樣靜靜地躺著狡蝶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贮勃。 梳的紋絲不亂的頭發(fā)上贪惹,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音寂嘉,去河邊找鬼奏瞬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泉孩,可吹牛的內(nèi)容都是我干的硼端。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寓搬,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼珍昨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起句喷,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤曼尊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后脏嚷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骆撇,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年父叙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了神郊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趾唱,死狀恐怖涌乳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甜癞,我是刑警寧澤夕晓,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站悠咱,受9級(jí)特大地震影響蒸辆,放射性物質(zhì)發(fā)生泄漏征炼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一躬贡、第九天 我趴在偏房一處隱蔽的房頂上張望谆奥。 院中可真熱鬧,春花似錦拂玻、人聲如沸酸些。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魄懂。三九已至,卻和暖如春闯第,著一層夾襖步出監(jiān)牢的瞬間市栗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工乡括, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肃廓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓诲泌,卻偏偏與公主長得像盲赊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子敷扫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354