【并發(fā)重要原則】happens-before理解和應(yīng)用

理解happens-before主要為了理解源碼。主要jdk里面一堆華麗呼哨的操作涂籽,如果基礎(chǔ)不牢靠吕喘,看著心累。

目錄:
1.happens-before的理解
2.應(yīng)用1矮固,futuretask non-volatile

引用:
1.why outcome object in FutureTask is non-volatile? (老外對(duì)代碼的理解真的厲害)
2.happens-before俗解
3. Java內(nèi)存模型之happens-before
4. 一句話理解什么是happens-before
5. happen-before規(guī)則及其對(duì)DCL的分析(含代碼)

1.happens-before的理解

1.1 為什么要有一個(gè)happens-before的原則失息?

結(jié)論:happens-before覺得著什么時(shí)候變量操作對(duì)你可見。

我們知道cpu的運(yùn)行極快,而讀取主存對(duì)于cpu而言有點(diǎn)慢了盹兢,在讀取主存的過(guò)程中cpu一直閑著(也沒數(shù)據(jù)可以運(yùn)行)邻梆,這對(duì)資源來(lái)說(shuō)造成極大的浪費(fèi)。所以慢慢的cpu演變成了多級(jí)cache結(jié)構(gòu)绎秒,cpu在讀cache的速度比讀內(nèi)存快了n倍浦妄。

當(dāng)線程在執(zhí)行時(shí),會(huì)保存臨界資源的副本到私有work memory中见芹,這個(gè)memory在cache中剂娄,修改這個(gè)臨界資源會(huì)更新work memory但并不一定立刻刷到主存中,那么什么時(shí)候應(yīng)該刷到主存中呢辆童?什么時(shí)候和其他副本同步宜咒?
而且編譯器為了提高指令執(zhí)行效率,是可以對(duì)指令重排序的把鉴,重排序后指令的執(zhí)行順序不一樣故黑,有可能線程2讀取某個(gè)變量時(shí),線程1還未進(jìn)行寫入操作庭砍。這就是線程可見性的來(lái)源场晶。

針對(duì)以上兩個(gè)問(wèn)題,JMM給出happens-before通用的規(guī)則(注意這僅對(duì)java而言怠缸,其他的就布吉島了)

1.2 happens-before原則有啥好處诗轻?

i = 1;       //線程A執(zhí)行
j = i ;      //線程B執(zhí)行

j 是否等于1呢?假定線程A的操作(i = 1)happens-before線程B的操作(j = i)揭北。
那么可以確定線程B執(zhí)行后j = 1 一定成立扳炬。
如果他們不存在happens-before原則,那么j = 1 不一定成立搔体。

(即使代碼是先執(zhí)行j=1,然后執(zhí)行j=i恨樟,也不一定j=1,主要看是否符合happens-before)

1.3 happens-before原則

  1. 如果操作1 happens-before 操作2,那么第操作1的執(zhí)行結(jié)果將對(duì)操作2可見疚俱,而且操作1的執(zhí)行順序排在第操作2之前劝术。
  2. 兩個(gè)操作之間存在happens-before關(guān)系,并不意味著一定要按照happens-before原則制定的順序來(lái)執(zhí)行呆奕。如果重排序之后的執(zhí)行結(jié)果與按照happens-before關(guān)系來(lái)執(zhí)行的結(jié)果一致养晋,那么這種重排序并不非法。

1.4如何判斷是否為 happens-before梁钾?

  • 程序次序規(guī)則: 在一個(gè)單獨(dú)的線程中绳泉,按照程序代碼的執(zhí)行流順序,(時(shí)間上)先執(zhí)行的操作happen—before(時(shí)間上)后執(zhí)行的操作
    同一個(gè)線程中前面的所有寫操作對(duì)后面的操作可見

  • 管理鎖定規(guī)則:一個(gè)unlock操作happen—before后面(時(shí)間上的先后順序)對(duì)同一個(gè)鎖的lock操作陈轿。
    如果線程1解鎖了monitor a圈纺,接著線程2鎖定了a秦忿,那么麦射,線程1解鎖a之前的寫操作都對(duì)線程2可見(線程1和線程2可以是同一個(gè)線程)

  • volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫操作happen—before后面(時(shí)間上)對(duì)該變量的讀操作蛾娶。
    如果線程1寫入了volatile變量v(臨界資源),接著線程2讀取了v潜秋,那么蛔琅,線程1寫入v及之前的寫操作都對(duì)線程2可見(線程1和線程2可以是同一個(gè)線程)

  • 線程啟動(dòng)規(guī)則:Thread.start()方法happen—before調(diào)用用start的線程前的每一個(gè)操作钠至。
    假定線程A在執(zhí)行過(guò)程中挂脑,通過(guò)執(zhí)行ThreadB.start()來(lái)啟動(dòng)線程B,那么線程A對(duì)共享變量的修改在接下來(lái)線程B開始執(zhí)行前對(duì)線程B可見奖恰。注意:線程B啟動(dòng)之后钩述,線程A在對(duì)變量修改線程B未必可見寨躁。

  • 線程終止規(guī)則:線程的所有操作都happen—before對(duì)此線程的終止檢測(cè),可以通過(guò)Thread.join()方法結(jié)束牙勘、Thread.isAlive()的返回值等手段檢測(cè)到線程已經(jīng)終止執(zhí)行职恳。
    (線程t1寫入的所有變量,在任意其它線程t2調(diào)用t1.join()方面,或者t1.isAlive() 成功返回后放钦,都對(duì)t2可見。)

  • 線程中斷規(guī)則:對(duì)線程interrupt()的調(diào)用 happen—before 發(fā)生于被中斷線程的代碼檢測(cè)到中斷時(shí)事件的發(fā)生恭金。
    (線程t1寫入的所有變量操禀,調(diào)用Thread.interrupt(),被打斷的線程t2横腿,可以看到t1的全部操作)

  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)happen—before它的finalize()方法的開始颓屑。
    (對(duì)象調(diào)用finalize()方法時(shí),對(duì)象初始化完成的任意操作耿焊,同步到全部主存同步到全部cache揪惦。)

  • 傳遞性:如果操作A happen—before操作B,操作B happen—before操作C搀别,那么可以得出A happen—before操作C丹擎。
    A h-b B , B h-b C 那么可以得到 A h-b C

1.5 一言以蔽之歇父,這些規(guī)則背后的道理

在程序運(yùn)行過(guò)程中蒂培,所有的變更會(huì)先在寄存器或本地cache中完成,然后才會(huì)被拷貝到主存以跨越內(nèi)存柵欄(本地或工作內(nèi)存到主存之間的拷貝動(dòng)作)榜苫,此種跨越序列或順序稱為happens-before护戳。
注:happens-before本質(zhì)是順序,重點(diǎn)是跨越內(nèi)存柵欄
通常情況下垂睬,寫操作必須要happens-before讀操作媳荒,即寫線程需要在所有讀線程跨越內(nèi)存柵欄之前完成自己的跨越動(dòng)作抗悍,其所做的變更才能對(duì)其他線程可見。

2.應(yīng)用

2.1 單例模式

單例模式可能存在問(wèn)題哦钳枕,請(qǐng)看我的文章【單例模式】DCL的問(wèn)題和解決方法

可以看出缴渊,如果有兩個(gè)線程都執(zhí)行過(guò)synchronized ,那么符合"管理鎖定規(guī)則"鱼炒,那么我們可以線程 singleton即使不加上volatile衔沼,也不會(huì)影響線程間的可見性

public class Singleton {     
    private static Singleton singleton;  
    private Singleton() {      }     
    public static Singleton getInstance() {     
        if (singleton == null) {    
            synchronized (Singleton.class) {     
                if (singleton == null) {     
                    Singleton temp = null;  
                    try {  
                        temp = new Singleton();    
                    } catch (Exception e) {   }  
                    if (temp != null) 
                        singleton = temp; 
                }    
            }    
        }    
        return singleton;    
    }  
}  

2.2 CopyOnWriteArrayList 的例子

線程A和線程B要執(zhí)行的以下代碼,最后結(jié)果b=1嗎昔瞧?(其中l(wèi)ist為CopyOnWriteArrayList)

線程A 線程B
a = 1; list.get(0);
list.set(1,""); int b = a;

執(zhí)行順序流1:

步驟 線程A 線程B
a a = 1;
b list.set(1,"");
c list.get(0);
d int b = a;

執(zhí)行順序流2:

步驟 線程A 線程B
a a = 1;
b list.get(0);
c list.set(1,"");
d int b = a;

在確線程B是否一定能看到線程A的a變量前指蚁,我們先看看CopyOnWriteArrayList 的源碼:
可以發(fā)現(xiàn)基本get/set都是一個(gè)volatile申明的array變量

    private transient volatile Object[] array;

    public E get(int index) {
        return get(getArray(), index);
    }
    final Object[] getArray() {
        return array;
    }

    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //TODO xxx
            setArray(newElements);
        } finally {
            lock.unlock();
    }
    final void setArray(Object[] a) {
        array = a;
    }    

通過(guò)我們的源碼的分析,基本可以判斷這里要用到volatile變量規(guī)則自晰,
即:對(duì)一個(gè)volatile變量的寫操作happen—before后面(時(shí)間上)對(duì)該變量的讀操作凝化。

我們對(duì)執(zhí)行順序流進(jìn)行分析:
(步驟a happens-before 步驟b 記為 hb(a,b))

順序流1:
根據(jù)程序次序規(guī)則可以得到 hb(a,b),hb(c,d)酬荞,如果我們希望b=1搓劫,那么只需要 hb(b,c)
由于volatile變量規(guī)則,我們可以得到hb(b,c)袜蚕,所以一定b=1糟把。

順序流2:
根據(jù)程序次序規(guī)則可以得到 hb(a,c),hb(b,d)牲剃,如果我們希望b=1遣疯,那么我們需要hb(a,b)或hb(c,d)。
然而沒有規(guī)則可以得到以上條件凿傅,故不成立缠犀,b不一定等于1。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末聪舒,一起剝皮案震驚了整個(gè)濱河市辨液,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌箱残,老刑警劉巖滔迈,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異被辑,居然都是意外死亡燎悍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門盼理,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谈山,“玉大人,你說(shuō)我怎么就攤上這事宏怔∽嗦罚” “怎么了畴椰?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸽粉。 經(jīng)常有香客問(wèn)我斜脂,道長(zhǎng),這世上最難降的妖魔是什么潜叛? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任秽褒,我火速辦了婚禮壶硅,結(jié)果婚禮上威兜,老公的妹妹穿的比我還像新娘。我一直安慰自己庐椒,他們只是感情好椒舵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著约谈,像睡著了一般笔宿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棱诱,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天泼橘,我揣著相機(jī)與錄音,去河邊找鬼迈勋。 笑死炬灭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的靡菇。 我是一名探鬼主播重归,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼厦凤!你這毒婦竟也來(lái)了鼻吮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤较鼓,失蹤者是張志新(化名)和其女友劉穎椎木,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體博烂,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡香椎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脖母。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片士鸥。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谆级,靈堂內(nèi)的尸體忽然破棺而出烤礁,到底是詐尸還是另有隱情讼积,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布脚仔,位于F島的核電站勤众,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鲤脏。R本人自食惡果不足惜们颜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望猎醇。 院中可真熱鬧窥突,春花似錦、人聲如沸硫嘶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)沦疾。三九已至称近,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哮塞,已是汗流浹背刨秆。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忆畅,地道東北人衡未。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像邻眷,于是被迫代替她去往敵國(guó)和親眠屎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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