0. 前言
介紹Happens-before規(guī)則前,需要對JMM(Java Memory Model)有一定了解(可以閱讀參考目錄中6-12(強烈推薦))。
簡單來說,程序員寫程序是按照順序一致性模型編寫代碼,目的是得到正確的執(zhí)行結(jié)果,即順序一致性模型下的執(zhí)行結(jié)果。但是出于執(zhí)行效率等原因,編譯器在編譯時、處理器在執(zhí)行時可能進行指令重排以提高程序運行效率,JMM保證在正確同步的情況下得到的結(jié)果與順序一致性模型得到的結(jié)果相同。

0.1 為什么會執(zhí)行結(jié)果可能會有不同?
在順序一致性模型中,只有一個單一的全局內(nèi)存,所有線程的操作直接在此內(nèi)存中反映,對后續(xù)執(zhí)行的線程可見。

但是在java中,系統(tǒng)的資源分配是以進程為單位,線程享有所在進程的資源,但是每個線程會有自己的工作內(nèi)存(包括但不限于寄存器、高速緩存等),工作內(nèi)存中的內(nèi)容是主內(nèi)存中內(nèi)容的拷貝,線程執(zhí)行時修改的是工作內(nèi)存中的內(nèi)容,這樣的話修改就不一定會被別的線程觀察到。

1. 并發(fā)的三大基礎(chǔ)概念
原子性、可見性、有序性
1.1 原子性
一個操作集合,要么全部執(zhí)行,要么全不執(zhí)行,即不能在執(zhí)行的中途中斷。
一般來說只有簡單的讀取、賦值(不包括變量間的賦值)、CAS操作才是原子操作。
tip:java中的long、double類型的變量的讀寫是不具有原子性的。
java中保證原子操作的手段:synchronized、lock、volatile(對long、double)、CAS等。
1.2 可見性
當(dāng)多個線程訪問共享變量時,某個線程對其作出修改后,其他線程是否能觀察到此修改。
因為線程執(zhí)行時,修改的是工作內(nèi)存的數(shù)據(jù),工作內(nèi)存對其他線程是不可見的,所以在此修改同步到主內(nèi)存之前,對其他線程來說,此修改都是不可見的。
java中保證可見性的手段:鎖、volatile。
其中可見性關(guān)系的描述即為Happens-before規(guī)則,如果A Happens-before B則A中的修改均對B可見。
1.3 有序性
程序的執(zhí)行順序按照代碼的先后順序執(zhí)行。
由于JMM允許編譯器和處理器進行指令重排來進行優(yōu)化,這種優(yōu)化如果沒有限制條件的話可能會影響多線程程序的執(zhí)行結(jié)果,所以要添加一定的限制條件,即Happens-before規(guī)則。
如果兩個操作具有Happens-before關(guān)系,則會保證他們的有序性。
2. Happens-before規(guī)則
Happens-before規(guī)則具體包含以下內(nèi)容:
- 程序次序規(guī)則:一個線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作;
- 鎖定規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖額lock操作;
- volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作;
- 傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C;
- 線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個一個動作;
- 線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生;
- 線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行;
- 對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始;
3. 總結(jié)
寫代碼時是按照順序一致性模型寫代碼,但是在實際情況下,由于工作內(nèi)存的存在和優(yōu)化的需求,多線程時程序的結(jié)果并不一定是想要的結(jié)果,為了避免這種情況發(fā)生,使用volatile/Lock等手段,保證滿足Happens-before規(guī)則的代碼的正確性,從而保證程序結(jié)果的正確性。
4. 參考
- happens-before俗解
- 【死磕Java并發(fā)】-----Java內(nèi)存模型之happens-before
- 【Java并發(fā)編程】之十六:深入Java內(nèi)存模型——happen-before規(guī)則及其對DCL的分析(含代碼)
- 【死磕Java并發(fā)】—–深入分析volatile的實現(xiàn)原理
- 你真的了解volatile關(guān)鍵字嗎?
- 深入理解Java內(nèi)存模型(一)——基礎(chǔ)
- 深入理解Java內(nèi)存模型(二)——重排序
- 深入理解Java內(nèi)存模型(三)——順序一致性
- 深入理解Java內(nèi)存模型(四)——volatile
- 深入理解Java內(nèi)存模型(五)——鎖
- 深入理解Java內(nèi)存模型(六)——final
- 深入理解Java內(nèi)存模型(七)——總結(jié)
- 線程的工作內(nèi)存
- 同一進程中的線程究竟共享哪些資源