『讀書筆記』Java并發(fā)編程的藝術(shù)(JMM內(nèi)存模型)

前言

本文并非按照書中目錄所寫酬凳,為自己讀后總結(jié)谋竖,個(gè)人覺得這本書有著比較深的學(xué)習(xí)價(jià)值红柱,在此致敬本書作者。

并發(fā)編程模型兩個(gè)關(guān)鍵問題

并發(fā)編程需要著手解決原子性蓖乘、有序性锤悄、可見性三個(gè)問題,這三個(gè)問題側(cè)重在線程通信與線程同步上嘉抒。針對(duì)于這兩個(gè)問題零聚,有兩種機(jī)制來保證: 共享內(nèi)存 | 消息傳遞。
共享內(nèi)存屏蔽通信細(xì)節(jié)些侍,但需要顯式指定線程同步順序隶症;消息傳遞由程序員主動(dòng)發(fā)送消息,顯式執(zhí)行線程通信岗宣,線程同步由于自帶發(fā)送順序蚂会,隱式進(jìn)行。

\ 線程通信 線程同步 典型語(yǔ)言
共享內(nèi)存 隱式 顯式 Java
消息傳遞 顯式 隱式 Go

原子性狈定、有序性颂龙、可見性

原子性:操作不可分割。CPU層面保證基礎(chǔ)指令的原子性纽什,對(duì)于復(fù)雜原子指令措嵌,比如交換指令CMPXCHG,采用總線鎖or緩存行鎖來保證原子性芦缰。需要注意的是企巢,32位操作系統(tǒng)不對(duì)64位數(shù)據(jù)寫入保證原子性,比如long類型或者double類型變量寫入让蕾。
有序性:涉及到的指令重排分三種浪规,編譯級(jí)指令重排(編譯器優(yōu)化)、指令級(jí)指令重排(CPU指令并行)探孝、 內(nèi)存系統(tǒng)指令重排(CPU讀/寫緩存區(qū))笋婿,單線程模型下,CPU與編譯器不會(huì)對(duì)有間接依賴的指令重排序顿颅。
可見性:針對(duì)上述三種指令重排缸濒,而引發(fā)線程之間的內(nèi)存可見性問題。

進(jìn)一步充電 緩存一致性協(xié)議之MESI

Java內(nèi)存模型的抽象結(jié)構(gòu)

JMM定義了共享變量存儲(chǔ)于主存之中粱腻,每個(gè)線程都有一個(gè)私有的本地內(nèi)存庇配,存儲(chǔ)共享變量的副本。這里的本地內(nèi)存是一個(gè)抽象的概念绍些,并不真實(shí)存在捞慌,它涵蓋了CPU高速緩存(L1,L2柬批,L3)啸澡、寫緩沖區(qū)、編譯器優(yōu)化等等氮帐。為了保證內(nèi)存可見锻霎,Java編譯器在生成指令序列的適當(dāng)位置插入內(nèi)存屏障。

JMM內(nèi)存屏障

JMM把內(nèi)存屏障指令分為4類揪漩,見下表旋恼。


JMM內(nèi)存屏障指令

上面這四個(gè)內(nèi)存屏障簡(jiǎn)單來說,Load用于讀取裝載數(shù)據(jù)奄容,Store用于存儲(chǔ)冰更,會(huì)保證前面的裝載or存儲(chǔ)<優(yōu)先于>后面的裝載or存儲(chǔ)

volatile內(nèi)存語(yǔ)義

當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)把該線程的本地內(nèi)存的共享變量值刷新到主存昂勒。
當(dāng)讀一個(gè)volatile變量時(shí)蜀细,JMM會(huì)把該線程的本地內(nèi)存置為無效,從主存獲取共享變量戈盈。

  • volatile寫之前的操作不會(huì)被編譯器重排序到volatile寫之后奠衔。
  • volatile讀之后的操作不會(huì)變編譯器重排序到volatile讀之前谆刨。
  • 當(dāng)?shù)谝粋€(gè)操作是volatile寫,第二個(gè)操作是volatile讀归斤,不能重排序痊夭。

為了實(shí)現(xiàn)volatile內(nèi)存語(yǔ)義,編譯器生成字節(jié)碼通過插入內(nèi)存屏障來禁止重排序

  • 在每個(gè)volatile寫前面插入StoreStore屏障脏里,確保volatile寫之前的數(shù)據(jù)刷新到主存她我,并且不會(huì)重排序到volatile寫之后。
  • 在每個(gè)volatile寫后面插入StoreLoad 屏障迫横,確保volatile寫與后續(xù)可能的volatile讀/寫操作重排序(這個(gè)開銷昂貴)番舆。
  • 在每個(gè)volatile讀后面插入LoadLoad 屏障,確保volatile讀不會(huì)與后續(xù)的普通讀重排序矾踱。
  • 在每個(gè)volatile讀后面插入LoadStore 屏障恨狈,確保volatile讀不會(huì)與后續(xù)的普通寫重排序。

比較有意思的是volatile寫之后的StoreLoad屏障呛讲,JMM可以選擇在每個(gè)volatile寫之后或者volatile讀之前插入StoreLoad屏障拴事,但由于通常共享變量讀多寫少,JMM最終選擇在volatile寫之后插入StoreLoad屏障圣蝎,來提供一定的性能提升刃宵。
上面內(nèi)存屏障插入策略非常保守,但它可以保證在任意處理器平臺(tái)徘公,任意程序中都能保證volatile的正確語(yǔ)義牲证。JMM針對(duì)不同平臺(tái)不同代碼,會(huì)省略部分內(nèi)存屏障來做優(yōu)化关面。

鎖(ReentrantLock)的內(nèi)存語(yǔ)義

  • 公平鎖與非公平鎖釋放時(shí)坦袍,都要寫volatile變量state。
  • 公平鎖獲取時(shí)等太,首先會(huì)讀volatile變量捂齐。
  • 非公平鎖獲取時(shí),首先CAS更新volatile變量缩抡。
    編譯器會(huì)為CAS的交換指令CMPXCHG加入lock前綴奠宜,lock前綴同時(shí)具有volatile讀與volatile寫的內(nèi)存語(yǔ)義。

總結(jié)來說:加鎖具有和volatile讀相同的內(nèi)存語(yǔ)義瞻想,解鎖具有和volatile寫相同的內(nèi)存語(yǔ)義压真。
并發(fā)包下的大部分鎖,同步器都是基于AQS實(shí)現(xiàn)的蘑险,并發(fā)包的基石是volatile滴肿、synchronize、cas佃迄,JUC的包有個(gè)通用的實(shí)現(xiàn)模式:首先聲明共享變量為volatile泼差,然后使用CAS原子更新實(shí)現(xiàn)線程之間同步贵少,同時(shí)配合CAS或volatile讀寫的內(nèi)存語(yǔ)義來實(shí)現(xiàn)線程之間的通信。

final域重排序規(guī)則

  • 編譯器會(huì)在final域?qū)懼蠖言担瑯?gòu)造函數(shù)返回之前插入StoreStore內(nèi)存屏障滔灶,禁止final域的寫重排序到構(gòu)造函數(shù)之外。
  • 初次讀包含final域的對(duì)象引用套啤,再初次讀final域,禁止重排序随常。這兩個(gè)操作之間存在間接依賴潜沦,大多數(shù)處理器本身就不會(huì)重排序,但也有少部分的處理器允許間接依賴的關(guān)系進(jìn)行重排序绪氛。
    final的語(yǔ)義保證了正確構(gòu)建的對(duì)象不需要使用同步唆鸡,其他線程都能看到正確的被初始化之后的值。
    以下為錯(cuò)誤示例代碼枣察,final引用從構(gòu)造函數(shù)溢出
/**
 * @author YuanChong
 * @create 2020-03-29 18:50
 * @desc final引用從構(gòu)造函數(shù)溢出示例
 */
public class FinalExample {
    private final int data;

    private static FinalExample ref;
    
    private FinalExample(int data) {
        this.data = data;
        ref = this;
    }
    
    public static void instanceObject() {
        new FinalExample(1);
    }

    /**
     * 并發(fā)下争占,A線程執(zhí)行instanceObject,B線程執(zhí)行readFinal序目,B線程讀到的可能是0也可能是1
     * @return
     */
    public static int readFinal() {
        return ref.data;
    }
}

JMM屏蔽內(nèi)存模型細(xì)節(jié)

JMM提供了as-if-serial語(yǔ)義與happens-before原則保證程序的正確執(zhí)行臂痕。
happens-before提供給程序員易于理解,簡(jiǎn)單易懂的并發(fā)下內(nèi)存可見性保證猿涨。
as-if-serial語(yǔ)義保證了不管怎么重排序握童,單線程程序的執(zhí)行結(jié)果不能被改變。
需要注意的是叛赚,這兩種語(yǔ)義只是JMM對(duì)程序員的保證承諾澡绩,JMM只保證執(zhí)行結(jié)果,但具體是否涉及重排序還要看編譯器與處理器的優(yōu)化俺附。這是JMM在編譯優(yōu)化與簡(jiǎn)單易懂的內(nèi)存模型之間的一個(gè)權(quán)衡結(jié)果肥卡。因此,happens-before更應(yīng)該理解成生效可見于事镣,他與執(zhí)行順序無關(guān)步鉴。

  • 程序順序原則:本線程的每個(gè)操作生效可見于后續(xù)發(fā)生的所有操作
  • 鎖規(guī)則:當(dāng)前線程解鎖生效可見于后續(xù)其他線程的加鎖
  • volatile規(guī)則:volatile寫生效可見于后續(xù)對(duì)volatile的讀
  • 傳遞性規(guī)則:如果A happens-before B,B happens-before C璃哟,那么A happens-before C
  • start規(guī)則:如果A線程執(zhí)行Thread.start()啟動(dòng)線程B唠叛,A線程的Thread.start()生效可見于B線程的后續(xù)操作
  • join規(guī)則:如果A線程執(zhí)行Thread.join(),B線程的任意操作生效可見于A從Thread.join()中返回

我們結(jié)合happens-before的幾個(gè)原則沮稚,可以分析出線程同步代碼是否有可見性問題
比如A線程執(zhí)行Thread.start()啟動(dòng)B線程艺沼,A線程做的共享變量的修改生效可見于B線程,這是由順序性規(guī)則蕴掏,start規(guī)則障般,傳遞性規(guī)則同時(shí)推斷出來的调鲸。

樓主之前也分析過鎖的happens-before推斷,詳見從happen-before角度分析synchronized與lock的內(nèi)存可見性問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挽荡,一起剝皮案震驚了整個(gè)濱河市藐石,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌定拟,老刑警劉巖于微,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異青自,居然都是意外死亡株依,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門延窜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恋腕,“玉大人,你說我怎么就攤上這事逆瑞≤伲” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵获高,是天一觀的道長(zhǎng)哈肖。 經(jīng)常有香客問我,道長(zhǎng)念秧,這世上最難降的妖魔是什么牡彻? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮出爹,結(jié)果婚禮上庄吼,老公的妹妹穿的比我還像新娘。我一直安慰自己严就,他們只是感情好总寻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梢为,像睡著了一般渐行。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铸董,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天祟印,我揣著相機(jī)與錄音,去河邊找鬼粟害。 笑死蕴忆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悲幅。 我是一名探鬼主播套鹅,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼站蝠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了卓鹿?” 一聲冷哼從身側(cè)響起菱魔,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吟孙,沒想到半個(gè)月后澜倦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杰妓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年藻治,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稚失。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡栋艳,死狀恐怖恰聘,靈堂內(nèi)的尸體忽然破棺而出句各,到底是詐尸還是另有隱情,我是刑警寧澤晴叨,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布凿宾,位于F島的核電站,受9級(jí)特大地震影響兼蕊,放射性物質(zhì)發(fā)生泄漏初厚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一孙技、第九天 我趴在偏房一處隱蔽的房頂上張望产禾。 院中可真熱鬧,春花似錦牵啦、人聲如沸亚情。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)楞件。三九已至,卻和暖如春裳瘪,著一層夾襖步出監(jiān)牢的瞬間土浸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工彭羹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黄伊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓派殷,卻偏偏與公主長(zhǎng)得像毅舆,于是被迫代替她去往敵國(guó)和親西篓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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