java多線程(四)---volatile內(nèi)存語義及實(shí)現(xiàn)

移步java多線程系列文章

1 cpu術(shù)語的定義

術(shù)語 英文單詞 術(shù)語描述
內(nèi)存屏障 memory barriers 是一組處理器指令,用于實(shí)現(xiàn)內(nèi)存操作的順序限制
緩沖行 cache line 緩存中可以分配的最小存儲(chǔ)單位。處理器填寫緩存線時(shí)會(huì)加載整個(gè)緩存線,需要使用多個(gè)主內(nèi)存讀周期
原子操作 atomic operations 不可中斷的一個(gè)或一系列操作
緩存行填充 cache line fill 當(dāng)處理器識(shí)別到從內(nèi)存中讀取操作數(shù)是可緩存的,處理器讀取整個(gè)緩存行到適當(dāng)?shù)木彺妫↙1,L2,L3或所有)
緩存命中 cache hit 如果進(jìn)行高速緩存行填充操作的內(nèi)存位置仍然是下次處理器訪問的地址時(shí),處理器從緩存中讀取操作數(shù),而不是從內(nèi)存讀取
寫命中 write hit 當(dāng)處理器將操作數(shù)寫回到一個(gè)內(nèi)存緩存的區(qū)域時(shí),它首先會(huì)檢查這個(gè)緩存的內(nèi)存地址是否在緩存行中,如果存在一個(gè)有效的緩存行,則處理器將這個(gè)操作數(shù)寫回到緩存,而不是寫回到內(nèi)存,這個(gè)操作被稱為寫命中
寫缺失 write misses the cache 一個(gè)有效的緩存行被寫入到不存在的內(nèi)存區(qū)域

2. volatile的定義

  • Java語言規(guī)范第3版中對(duì)volatile的定義如下:

  • Java編程語言允許線程訪問共享變量,為了確保共享變量能被準(zhǔn)確和一致地更新,線程應(yīng)該確保通過排他鎖單獨(dú)獲得這個(gè)變量。Java語言提供了volatile,在某些情況下比鎖要更加方便。如果一個(gè)字段被聲明成volatile,Java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的。

  • 即使是64位的long型和double型變量,只要它是volatile變量,對(duì)該變量的讀/寫就具有原子性。如果是多個(gè)volatile操作或類似于volatile++這種復(fù)合操作,這些操作整體上不具原子性

  • volatile變量自身具有一下特性:
  • 可見性。對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入。
  • 原子性:對(duì)任意單個(gè)volatile變量的讀/寫具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性。

3 volatile寫-讀的內(nèi)存語義

  • volatile寫的內(nèi)存語義:當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存
  • volatile讀的內(nèi)存語義:當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。
  • volatile重排序規(guī)。


    volatile重排序規(guī)則表.jpg

    從上表可以看出

  • 當(dāng)?shù)诙€(gè)操作是volatile寫時(shí),不管第一個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則確保volatile寫之前的操作不會(huì)被編譯器重排序到volatile寫之后。
  • 當(dāng)?shù)谝粋€(gè)操作是volatile讀時(shí),不管第二個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則確保volatile讀之后的操作不會(huì)被編譯器重排序到volatile讀之前。
  • 當(dāng)?shù)谝粋€(gè)操作是volatile寫,第二個(gè)操作是volatile讀時(shí),不能重排序。

4 volatile 內(nèi)存語義的實(shí)現(xiàn)

  • 為了實(shí)現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時(shí),會(huì)在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。
  • 對(duì)于編譯器來說,發(fā)現(xiàn)一個(gè)最優(yōu)布置來最小化插入屏障的總數(shù)幾乎不可能。

4.1 JMM采取保守策略

  • JMM采取保守策略。下面是基于保守策略的JMM內(nèi)存屏障插入策略
  • 在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障。
  • 在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障。
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障
  • 上述內(nèi)存屏障插入策略非常保守,但它可以保證在任意處理器平臺(tái),任意的程序中都能得到正確的volatile內(nèi)存語義

4.2 volatile寫插入內(nèi)存屏障后生成的指令序列

qq_pic_merged_1533870644878.jpg
  • StoreStore屏障可以保證在volatile寫之前,其前面的所有普通寫操作已經(jīng)對(duì)任意處理器可見了。這是因?yàn)镾toreStore屏障將保障上面所有的普通寫在volatile寫之前刷新到主內(nèi)存。
  • volatile寫后面的StoreLoad屏障。此屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序。因?yàn)榫幾g器常常無法準(zhǔn)確判斷在一個(gè)volatile寫的后面是否需要插入一個(gè)StoreLoad屏障(比如,一個(gè)volatile寫之后方法立即return)。
  • 為了保證能正確實(shí)現(xiàn)volatile的內(nèi)存語義,JMM在采取了保守策略:在每個(gè)volatile寫的后面,或者在每個(gè)volatile讀的前面插入一個(gè)StoreLoad屏障。
  • 從整體執(zhí)行效率的角度考慮,JMM最終選擇了在每個(gè)volatile寫的后面插入一個(gè)StoreLoad屏障。因?yàn)関olatile寫-讀內(nèi)存語義的常見使用模式是:一個(gè)寫線程寫volatile變量,多個(gè)讀線程讀同一個(gè)volatile變量。
  • 當(dāng)讀線程的數(shù)量大大超過寫線程時(shí),選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執(zhí)行效率的提升。從這里可以看到JMM在實(shí)現(xiàn)上的一個(gè)特點(diǎn):首先確保正確性,然后再去追求執(zhí)行效率。

4.3 volatile讀插入內(nèi)存屏障后生成的指令序列

volatile讀插入內(nèi)存屏障后生成的指令序列.jpg
  • LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序。
  • LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序。
  • 在實(shí)際執(zhí)行時(shí),只要不改變volatile寫-讀的內(nèi)存語義,編譯器可以根據(jù)具體情況省略不必要的屏障。

4.4示例

class VolatileBarrierExample {
       int a;
       volatile int v1 = 1;
       volatile int v2 = 2;
       void readAndWrite() {
           int i = v1;      // 第一個(gè)volatile讀
           int j = v2;       // 第二個(gè)volatile讀
           a = i + j;         // 普通寫
           v1 = i + 1;       // 第一個(gè)volatile寫
           v2 = j * 2;       // 第二個(gè) volatile寫
       }
       …               // 其他方法
}

針對(duì)readAndWrite()方法,編譯器在生成字節(jié)碼時(shí)可以做如下的優(yōu)化。


qq_pic_merged_1533871374155.jpg
  • 注意,最后的StoreLoad屏障不能省略。因?yàn)榈诙€(gè)volatile寫之后,方法立即return。此時(shí)編譯器可能無法準(zhǔn)確斷定后面是否會(huì)有volatile讀或?qū)懀瑸榱税踩鹨?,編譯器通常會(huì)在這里插入一個(gè)StoreLoad屏障。
  • 上面的優(yōu)化針對(duì)任意處理器平臺(tái)

4.5 不同的處理器(X86)

  • 由于不同的處理器有不同“松緊度”的處理器內(nèi)存模型,內(nèi)存屏障的插入還可以根據(jù)具體的處理器內(nèi)存模型繼續(xù)優(yōu)化。
  • 以X86處理器為例,除最后的StoreLoad屏障外,其他的屏障都會(huì)被省略。
  • X86處理器僅會(huì)對(duì)寫-讀操作做重排序。X86不會(huì)對(duì)讀-讀、讀-寫和寫-寫操作做重排序,因此在X86處理器中會(huì)省略掉這3種操作類型對(duì)應(yīng)的內(nèi)存屏障。
  • 在X86中,JMM僅需在volatile寫后面插入一個(gè)StoreLoad屏障即可正確實(shí)現(xiàn)volatile寫-讀的內(nèi)存語義。這意味著在X86處理器中,volatile寫的開銷比volatile讀的開銷會(huì)大很多(因?yàn)閳?zhí)行StoreLoad屏障開銷會(huì)比較大)
qq_pic_merged_1533871668360.jpg

5 其他

  • 在JSR-133之前的舊Java內(nèi)存模型中,雖然不允許volatile變量之間重排序,但舊的Java內(nèi)存模型允許volatile變量與普通變量重排序。
  • JSR-133專家組決定增強(qiáng)volatile的內(nèi)存語義:嚴(yán)格限制編譯器和處理器對(duì)volatile變量與普通變量的重排序,確保volatile的寫-讀和鎖的釋放-獲取具有相同的內(nèi)存語義。從編譯器重排序規(guī)則和處理器內(nèi)存屏障插入策略來看,只要volatile變量與普通變量之間的重排序可能會(huì)破壞volatile的內(nèi)存語義,這種重排序就會(huì)被編譯器重排序規(guī)則和處理器內(nèi)存屏障插入策略禁止。
  • 由于volatile僅僅保證對(duì)單個(gè)volatile變量的讀/寫具有原子性,而鎖的互斥執(zhí)行的特性可以確保對(duì)整個(gè)臨界區(qū)代碼的執(zhí)行具有原子性。在功能上,鎖比volatile更強(qiáng)大;在可伸縮性和執(zhí)行性能上,volatile更有優(yōu)勢(shì)。

參考

《java并發(fā)編程的藝術(shù)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評(píng)論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評(píng)論 1 285
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容

  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,664評(píng)論 2 17
  • 秦淮河畔,有家米店。 米店南面緊挨著秦淮河,門面朝北,正對(duì)著秦淮老街。米店旁,有棵上了年紀(jì)的銀杏樹。據(jù)...
    拾荒野人閱讀 291評(píng)論 0 0
  • 善自重而不輕人 敏羞恥而不取辱 識(shí)小人而不小人 辨陰險(xiǎn)而不陰險(xiǎn) 這是有故事而不世故的人,處理事情的標(biāo)準(zhǔn) 信息爆炸的...
    固執(zhí)的小丫頭閱讀 231評(píng)論 0 0