
該文章屬于《Java并發(fā)編程》系列文章,如果想了解更多,請(qǐng)點(diǎn)擊《Java并發(fā)編程之總目錄》
一、并發(fā)的起源
為了提高計(jì)算機(jī)處理數(shù)據(jù)的速度?,F(xiàn)代的計(jì)算機(jī)都支持多任務(wù)處理。在32位windows操作系統(tǒng)中 ,多任務(wù)處理是指系統(tǒng)可同時(shí)運(yùn)行多個(gè)進(jìn)程,而每個(gè)進(jìn)程也可同時(shí)執(zhí)行多個(gè)線程。一個(gè)線程是指程序的一條執(zhí)行路徑,它在系統(tǒng)指定的時(shí)間片中完成特定的功能。系統(tǒng)不停地在多個(gè)線程之間切換,由于時(shí)間很短,看上去多個(gè)線程在同時(shí)運(yùn)行?;蛘邔?duì)于在線程序可并行執(zhí)行同時(shí)服務(wù)于多個(gè)用戶稱為多任務(wù)處理。
二、物理計(jì)算機(jī)的內(nèi)存模型
在理解java內(nèi)存模型之前,我們先來(lái)了解一下,物理計(jì)算機(jī)的內(nèi)存模型,其對(duì)Java內(nèi)存模型有著很大的參考意義。
在物理計(jì)算機(jī)中,我們需要處理的數(shù)據(jù)都在內(nèi)存中,處理器處理數(shù)據(jù),需要從內(nèi)存中獲取相應(yīng)的數(shù)據(jù),然后存入內(nèi)存中,為了提高計(jì)算機(jī)的處理速度(讀取數(shù)據(jù),存儲(chǔ)數(shù)據(jù)有IO消耗),我們常常會(huì)在CPU(處理器)中加入高速緩存(Cache Memory),也就是將數(shù)據(jù)緩存到處理器中,當(dāng)處理器處理完數(shù)據(jù)后,再將處理的數(shù)據(jù)結(jié)果存儲(chǔ)在內(nèi)存中。具體如下圖所示:

當(dāng)CPU(處理器)要讀取一個(gè)數(shù)據(jù)時(shí),首先從一級(jí)緩存中查找,如果沒(méi)有找到再?gòu)亩?jí)緩存中查找,如果還是沒(méi)有就從三級(jí)緩存或內(nèi)存中查找。一般來(lái)說(shuō),每級(jí)緩存的命中率大概都在80%左右,也就是說(shuō)全部數(shù)據(jù)量的80%都可以在一級(jí)緩存中找到,只剩下20%的總數(shù)據(jù)量才需要從二級(jí)緩存、三級(jí)緩存或內(nèi)存中讀取。
高速緩存(Cache Memory)是位于CPU與內(nèi)存之間的臨時(shí)存儲(chǔ)器,它的容量比內(nèi)存小的多但是交換速度卻比內(nèi)存要快得多。高速緩存的出現(xiàn)主要是為了解決CPU運(yùn)算速度與內(nèi)存讀寫速度不匹配的矛盾,因?yàn)镃PU運(yùn)算速度要比內(nèi)存讀寫速度快很多,這樣會(huì)使CPU花費(fèi)很長(zhǎng)時(shí)間等待數(shù)據(jù)到來(lái)或把數(shù)據(jù)寫入內(nèi)存。在緩存中的數(shù)據(jù)是內(nèi)存中的一小部分,但這一小部分是短時(shí)間內(nèi)CPU即將訪問(wèn)的,當(dāng)CPU調(diào)用大量數(shù)據(jù)時(shí),就可先緩存中調(diào)用,從而加快讀取速度。
2.1 物理計(jì)算機(jī)的數(shù)據(jù)緩存不一致的問(wèn)題
雖然高速緩緩沖提高了CPU(處理器)處理數(shù)據(jù)的速度問(wèn)題。在多線程中運(yùn)行就會(huì)有問(wèn)題了。在多核CPU中,每條線程可能運(yùn)行于不同的CPU中,因此每個(gè)線程運(yùn)行時(shí)有自己的高速緩存(對(duì)單核CPU來(lái)說(shuō),其實(shí)也會(huì)出現(xiàn)這種問(wèn)題,只不過(guò)是以線程調(diào)度的形式來(lái)分別執(zhí)行的)。這時(shí)CPU緩存中的值可能和緩存中的值不一樣,這就會(huì)出現(xiàn)緩存不一致的問(wèn)題。為了解決該問(wèn)題。物理機(jī)算計(jì)提供了兩種方案來(lái)解決該問(wèn)題。具體如下圖所示:

2.1.1 通過(guò)總線加LOCK#鎖的方式
總線(Bus)是計(jì)算機(jī)各種功能部件之間傳送信息的公共通信干線,它是由導(dǎo)線組成的傳輸線束,在計(jì)算機(jī)中數(shù)據(jù)是通過(guò)總線,在處理器和內(nèi)存之間傳遞。

在早期的CPU當(dāng)中,是通過(guò)在總線上加LOCK#鎖的形式來(lái)解決緩存不一致的問(wèn)題。因?yàn)镃PU和其他部件進(jìn)行通信都是通過(guò)總線來(lái)進(jìn)行的,如果對(duì)總線加LOCK#鎖的話,也就是說(shuō)阻塞了其他CPU對(duì)其他部件訪問(wèn)(如內(nèi)存),從而使得只能有一個(gè)CPU能使用這個(gè)變量的內(nèi)存。在總線上發(fā)出了LCOK#鎖的信號(hào),那么只有等待這段代碼完全執(zhí)行完畢之后,其他CPU才能從其內(nèi)存讀取變量,然后進(jìn)行相應(yīng)的操作。這樣就解決了緩存不一致的問(wèn)題。
2.1.2 通過(guò)緩存一致性協(xié)議
但是由于在鎖住總線期間,其他CPU無(wú)法訪問(wèn)內(nèi)存,會(huì)導(dǎo)致效率低下。因此出現(xiàn)了第二種解決方案,通過(guò)緩存一致性協(xié)議來(lái)解決緩存一致性問(wèn)題。最出名的就是Intel 的MESI協(xié)議,MESI協(xié)議保證了每個(gè)緩存中使用的共享變量的副本是一致的。它核心的思想是:當(dāng)CPU寫數(shù)據(jù)時(shí),如果發(fā)現(xiàn)操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會(huì)發(fā)出信號(hào)通知其他CPU將該變量的緩存行置為無(wú)效狀態(tài),因此當(dāng)其他CPU需要讀取這個(gè)變量時(shí),發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無(wú)效的,那么它就會(huì)從內(nèi)存重新讀取。
2.2 CPU(處理器)的亂序執(zhí)行(out-of-orderexecution)
除了使用高速緩存來(lái)提高CPU(處理器)的數(shù)據(jù)處理速度,CPU(處理器)還采用了允許將多條指令不按程序規(guī)定的順序分開(kāi)發(fā)送給各相應(yīng)電路單元處理的技術(shù)。在這期間不按規(guī)定順序執(zhí)行指令,然后由重新排列單元將各執(zhí)行單元結(jié)果按指令順序重新排列。采用亂序執(zhí)行技術(shù)的目的是為了使CPU內(nèi)部電路滿負(fù)荷運(yùn)轉(zhuǎn)并相應(yīng)提高了CPU的運(yùn)行程序的速度。有可能大家不好理解。下面這個(gè)例子幫助大家理解。
假如請(qǐng)A、B、C三個(gè)名人為晚會(huì)題寫橫幅“春節(jié)聯(lián)歡晚會(huì)”六個(gè)大字,每人各寫兩個(gè)字。如果這時(shí)在一張大紙上按順序由A寫好"春節(jié)"后再交給B寫"聯(lián)歡",然后再由C寫"晚會(huì)",那么這樣在A寫的時(shí)候,B和C必須等待,而在B寫的時(shí)候C仍然要等待而A已經(jīng)沒(méi)事了。

但如果采用三個(gè)人分別用三張紙同時(shí)寫的做法, 那么B和C都不必須等待就可以同時(shí)各寫各的了,甚至C和B還可以比A先寫好也沒(méi)關(guān)系(就象亂序執(zhí)行),但當(dāng)他們都寫完后就必須重新在橫幅上(自然可以由別人做,就象CPU中亂序執(zhí)行后的重新排列單元)按"春節(jié)聯(lián)歡晚會(huì)"的順序排好才能掛出去。

三、Java的內(nèi)存模型
看到這里大家一定會(huì)發(fā)現(xiàn),我們所討論的CPU高速緩存、指令重排序等內(nèi)容都是計(jì)算機(jī)體系結(jié)構(gòu)方面的東西,并不是Java語(yǔ)言所特有的。事實(shí)上,很多主流程序語(yǔ)言(如C/C++)都存在緩存不一致的問(wèn)題,這些語(yǔ)言是借助物理硬件和操作系統(tǒng)的內(nèi)存模型來(lái)處理緩存不一致問(wèn)題的,因此不同平臺(tái)上內(nèi)存模型的差異,會(huì)影響到程序的執(zhí)行結(jié)果。Java虛擬機(jī)規(guī)范定義了自己的內(nèi)存模型JMM(Java Memory Model)來(lái)屏蔽掉不同硬件和操作系統(tǒng)的內(nèi)存模型差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)結(jié)果。所以對(duì)于Java程序員,無(wú)需了解底層硬件和操作系統(tǒng)內(nèi)存模型的知識(shí),只要關(guān)注Java自己的內(nèi)存模型,就能夠解決這些問(wèn)題啦。
Java內(nèi)存模型如下圖所示:

- 主內(nèi)存:主要存儲(chǔ)變量(包括。實(shí)例字段,靜態(tài)字段和構(gòu)成對(duì)象的元素)
- 工作內(nèi)存:每個(gè)線程都有自己的工作內(nèi)存,存儲(chǔ)了對(duì)應(yīng)的引用,方法參數(shù)。
如果對(duì)應(yīng)與Java內(nèi)存中堆與棧的概念的話,主內(nèi)存對(duì)應(yīng)Java內(nèi)存中的堆,工作內(nèi)存對(duì)應(yīng)Java虛擬機(jī)的棧。
3.1 內(nèi)存之間交互
主內(nèi)存與工作內(nèi)存之間的內(nèi)存交互,也就是從線程的私有內(nèi)存數(shù)據(jù)同步到主內(nèi)存中,從主內(nèi)存的讀取數(shù)據(jù)到線程的私有內(nèi)存中。Java內(nèi)存模型定義了8種操作來(lái)完成。虛擬機(jī)在實(shí)現(xiàn)時(shí)保證下面提到的每一種操作都是原子的,不可再分的。

- lock:作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。
- unlock:作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才能被其他線程訪問(wèn)。
- read:作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,一遍隨后的load動(dòng)作使用。
- load:作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入到工作內(nèi)存變量副本中。
- use:作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)會(huì)執(zhí)行這個(gè)操作。
- assign:作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎收到的值賦給工作內(nèi)存的變量。每當(dāng)虛擬機(jī)遇到給變量賦值的字節(jié)碼指令時(shí)會(huì)執(zhí)行這個(gè)操作。
- store:作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量值傳送到主內(nèi)存中。以便隨后的write操作。
- write:作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值,放入主內(nèi)存的變量中。
3.2 八種原子操作規(guī)則
既然Java內(nèi)存模型規(guī)定了內(nèi)存之間交互的一些操作。那么我們來(lái)看看,它到底擁有哪些規(guī)則呢。
- 不允許read和load、store和write操作之一單獨(dú)出現(xiàn)。即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受。或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況
- 不允許一個(gè)線程丟棄它的最近的assign操作。即變量在工作內(nèi)存改變了后必須把該變化同步到主內(nèi)存中。
- 不允許沒(méi)有發(fā)生任何的assign操作就把數(shù)據(jù)同步到主內(nèi)存中。
- 一個(gè)新的變量只能在主內(nèi)存中誕生,工作內(nèi)存要使用或者賦值。必須要經(jīng)過(guò)load或assign操作。
- 一個(gè)變量在同一時(shí)刻只允許一條線程進(jìn)行l(wèi)ock操作,但lock操作可以被同一線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖。
- 如果對(duì)一個(gè)變量進(jìn)行l(wèi)ock操作后,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行l(wèi)oad或assign操作。
- 如果一個(gè)變量事先沒(méi)有被lock操作鎖定,那就不允許對(duì)它進(jìn)行unlock操作。也不允許去unlock一個(gè)被其他線程鎖定的變量。
- 對(duì)一個(gè)變量執(zhí)行unLock操作之前,必須要把次變量同步到主內(nèi)存中(執(zhí)行store,write操作)。
上述規(guī)則規(guī)定了Java內(nèi)存之間交互的流程。保證了數(shù)據(jù)在單線程情形下傳輸過(guò)程中的準(zhǔn)確性與數(shù)據(jù)一致性。
四、重排序
前面提到過(guò),CPU(處理器)為了提高處理數(shù)據(jù)的速度,會(huì)進(jìn)行亂序執(zhí)行(out-of-orderexecution)。也就是重排序。但是CPU不會(huì)對(duì)任務(wù)操作進(jìn)行重排序,編譯器與處理器只會(huì)對(duì)沒(méi)有數(shù)據(jù)依賴性的指令進(jìn)行重排序。這里提到了一個(gè)關(guān)鍵詞數(shù)據(jù)依賴性。什么是數(shù)據(jù)依賴呢?
4.1 數(shù)據(jù)依賴
如果兩個(gè)操作訪問(wèn)同一個(gè)變量,且這兩個(gè)操作中有一個(gè)為寫操作,此時(shí)這兩個(gè)操作之間就存在數(shù)據(jù)依賴性。如下圖所示:
| 名稱 | 代碼示例 | 說(shuō)明 |
|---|---|---|
| 寫后讀 | a=1;b=a | 寫一個(gè)變量之后,再讀這個(gè)位置 |
| 寫后寫 | a=1;a=2 | 寫一個(gè)變量之后,再寫這個(gè)位置 |
| 讀后寫 | a=b;b=1 | 讀一個(gè)變量之后,再寫這個(gè)位置 |
上述三種情況,a與b存在著“數(shù)據(jù)依賴性”,同時(shí)大家也要注意。這里所說(shuō)的數(shù)據(jù)依賴性是指單個(gè)處理器執(zhí)行的指令序列和單個(gè)線程中執(zhí)行的操作。多處理器和不同線程之間是沒(méi)有數(shù)據(jù)依賴性這種關(guān)系的。
4.2 重排序規(guī)則(as-if-serial)
既然我們已經(jīng)知道了CPU在處理數(shù)據(jù)時(shí)候會(huì)出現(xiàn)重排序。那重排序的規(guī)則是什么呢?重排序規(guī)則:不管怎么重排序(編譯器和處理器為了提高并行度),單線程(程序)執(zhí)行結(jié)果不能被改變。編譯器、runtime和處理器都必須遵守。那么我們?nèi)切蚊娣e示例代碼說(shuō)明:
double a = 3;//底
double h = 10;//高
double s = a*h/2//面積
其中上述代碼的依賴關(guān)系如下圖所示:

如上圖所示:a與s存在數(shù)據(jù)依賴關(guān)系,同時(shí)h與s也存在依賴關(guān)系。因此在程序的最終指令執(zhí)行時(shí)。s是不能排在a與h之前。因?yàn)閍與h不存在著數(shù)據(jù)依賴關(guān)系。所以處理器可以對(duì)a與h之前的執(zhí)行順序重排序。

經(jīng)過(guò)處理器的重排序后,執(zhí)行的結(jié)果并沒(méi)有發(fā)生改變。
五、Java內(nèi)存模型的需要解決的問(wèn)題
前面我們已經(jīng)了解了Java內(nèi)存模型的大致結(jié)構(gòu)與操作方式,那么我們來(lái)看看Java內(nèi)存模型需要解決的問(wèn)題。
5.1 工作內(nèi)存的可見(jiàn)性問(wèn)題
工作內(nèi)存的可見(jiàn)性問(wèn)題(這里和計(jì)算機(jī)硬件的緩存不一致是一樣的道理)。從上文的Java內(nèi)存模型分析。我們已經(jīng)知道了當(dāng)多個(gè)線程操作同一個(gè)共享變量時(shí),如果一個(gè)線程修改了其中的變量的值(如果通過(guò)Java內(nèi)存模型的原子操作來(lái)表達(dá),一個(gè)線程多次use與assign 操作,而另一個(gè)線程經(jīng)過(guò)read、load之后,另一線程任然保持著之前從主內(nèi)存中獲取的值),另一個(gè)線程怎么感知呢?
5.2 重排序帶來(lái)的問(wèn)題
CPU(處理器)的重排序會(huì)對(duì)多線程帶來(lái)問(wèn)題。具體問(wèn)題我們用下列偽代碼來(lái)闡述:
public class Demo {
private int a = 0;
private boolean isInit = false;
private Config config;
public void init() {
config = readConfig();//1
isInit = true;//2
}
public void doSomething() {
if (isInit) {//3
doSomethingWithconfig();//4
}
}
}
isInit用來(lái)標(biāo)志是否已經(jīng)初始化配置。其中1,2操作是沒(méi)有數(shù)據(jù)依賴性,同理3、4操作也是沒(méi)有數(shù)據(jù)依賴性的。那么CPU(處理器)可能對(duì)1、2操作進(jìn)行重排序。對(duì)3、4操作進(jìn)行重排序?,F(xiàn)在我們加入線程A操作Init()方法,線程B操作doSomething()方法,那么我們看看重排序?qū)Χ嗑€程情況下的影響。

上圖中2操作排在了1操作前面。當(dāng)CPU時(shí)間片轉(zhuǎn)到線程B。線程B判斷 if (isInit)為true,接下來(lái)接著執(zhí)行 doSomethingWithconfig(),但是我們Config還沒(méi)有初始化。所以在多線程的情況下。重排序會(huì)影響程序的執(zhí)行結(jié)果。
六、Happens-Before 原則
上面我們討論了Java內(nèi)存模型需要解決的問(wèn)題,那Java有不有一個(gè)良好的解決辦法來(lái)處理以上出現(xiàn)的情況呢?答案是當(dāng)然的。為了方便程序員開(kāi)發(fā),將底層的煩瑣細(xì)節(jié)屏蔽掉,JMM定義了Happens-Before原則。只要我們理解了Happens-Before原則,無(wú)需了解Java內(nèi)存模型的內(nèi)存操作,就可以解決這些問(wèn)題(避免工作內(nèi)存的不可見(jiàn)與重排序帶來(lái)的問(wèn)題)。
Happens-Before原則是一組偏序關(guān)系:對(duì)于兩個(gè)操作A和B,這兩個(gè)操作可以在不同的線程中執(zhí)行。如果A Happens-Before B,那么可以保證,當(dāng)A操作執(zhí)行完后,A操作的執(zhí)行結(jié)果對(duì)B操作是可見(jiàn)的。那么有哪些滿足Happens-Before原則的呢?下面是Java內(nèi)存模型規(guī)定的一些規(guī)則。
6.1 程序次序規(guī)則
在一個(gè)線程內(nèi),按照程序代碼順序,書(shū)寫在前面的操作先行發(fā)生于書(shū)寫在后面的操作。這是因?yàn)镴ava語(yǔ)言規(guī)范要求Java內(nèi)存模型在單個(gè)線程內(nèi)部要維護(hù)類似嚴(yán)格串行的語(yǔ)義,如果多個(gè)操作之間有先后依賴關(guān)系,則不允許對(duì)這些操作進(jìn)行重排序。
6.2 鎖定規(guī)則
對(duì)一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。
public class Demo {
private int value;
public synchronized void setValue(int value) {
this.value = value;
}
public synchronized int getValue() {
return value;
}
}
上面這段代碼,setValue與getValue擁有同一個(gè)鎖(也就是當(dāng)前實(shí)例對(duì)象),假設(shè)setValue方法在線程A中執(zhí)行,getValue方法在線程B中執(zhí)行。線程A調(diào)用setValue方法會(huì)先對(duì)value變量賦值,然后釋放鎖。線程B調(diào)用getValue方法會(huì)先獲取到同一個(gè)鎖后,再讀取value的值。那么B線程獲取的value的值一定是正確的。
6.3 volatlie變量規(guī)則
對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面這個(gè)變量的讀操作。
public class Demo {
private volatile boolean flag;
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean isFlag() {
return flag;
}
}
上面這段代碼,假設(shè)setFlag方法在線程A中執(zhí)行,isFlag方法在線程B中執(zhí)行。線程A調(diào)用setFlag方法會(huì)先對(duì)value變量賦值,然后釋放鎖。線程B調(diào)用isFlag方法再讀取value的值。那么B線程獲取的flag的值一定是正確的。這里我們先不對(duì)volatlie進(jìn)行講解,后面系列文章會(huì)描述。
6.4 線程啟動(dòng)規(guī)則
Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)動(dòng)作。
start方法和新線程中的動(dòng)作一定是在兩個(gè)不同的線程中執(zhí)行。線程啟動(dòng)規(guī)則可以這樣去理解:調(diào)用start方法時(shí),會(huì)將start方法之前所有操作的結(jié)果同步到主內(nèi)存中,新線程創(chuàng)建好后,需要從主內(nèi)存獲取數(shù)據(jù)。這樣在start方法調(diào)用之前的所有操作結(jié)果對(duì)于新創(chuàng)建的線程都是可見(jiàn)的。
6.5 線程終止規(guī)則
線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)。
這里理解比較抽象。舉個(gè)例子,假設(shè)兩個(gè)線程s、t。在線程s中調(diào)用t.join()方法。則線程s會(huì)被掛起,等待t線程運(yùn)行結(jié)束才能恢復(fù)執(zhí)行。當(dāng)t.join()成功返回時(shí),s線程就知道t線程已經(jīng)結(jié)束了。在t線程中對(duì)共享變量的修改,對(duì)s線程都是可見(jiàn)的。類似的還有Thread.isAlive方法也可以檢測(cè)到一個(gè)線程是否結(jié)束。也就是說(shuō)當(dāng)一個(gè)線程結(jié)束時(shí),會(huì)把自己所有操作的結(jié)果都同步到主內(nèi)存。而任何其它線程當(dāng)發(fā)現(xiàn)這個(gè)線程已經(jīng)執(zhí)行結(jié)束了,就會(huì)從主內(nèi)存中重新刷新最新的變量值。所以結(jié)束的線程A對(duì)共享變量的修改,對(duì)于其它檢測(cè)了A線程是否結(jié)束的線程是可見(jiàn)的。
6.6 線程中斷規(guī)則
對(duì)線程interrupt()方法的調(diào)用先與被中斷線程的代碼檢查到中斷事件的發(fā)生。
假設(shè)兩個(gè)線程A和B,A先做了一些操作operationA,然后調(diào)用B線程的interrupt方法。當(dāng)B線程感知到自己的中斷標(biāo)識(shí)被設(shè)置時(shí)(通過(guò)拋出InterruptedException,或調(diào)用interrupted和isInterrupted),operationA中的操作結(jié)果對(duì)B都是可見(jiàn)的。
6.7 對(duì)象終結(jié)規(guī)則
一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始。
6.8 傳遞性規(guī)則
如果操作A先行與發(fā)生于操作B,操作B先行發(fā)生于操作C,那么就可以得出A先行發(fā)生于操作C的結(jié)論。
總結(jié)
- 在物理計(jì)算機(jī)中CPU為了提高處理速度,添加了高速緩存與CPU亂序執(zhí)行
- Java定義了自身的內(nèi)存模型是為了屏蔽掉不同硬件和操作系統(tǒng)的內(nèi)存模型差異
- Java為了處理內(nèi)存的不可見(jiàn)性與重排序的問(wèn)題,定義了Happens-Before 原則
- Happens-Before 原則的理解:對(duì)于兩個(gè)操作A和B,這兩個(gè)操作可以在不同的線程中執(zhí)行。如果A Happens-Before B,那么可以保證,當(dāng)A操作執(zhí)行完后,A操作的執(zhí)行結(jié)果對(duì)B操作是可見(jiàn)的。