前言本文中部分內(nèi)容引用至《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)》第12章,如果有興趣可自行深入閱讀,文末放有書(shū)籍PDF版本連接。 一、物理機(jī)中的并發(fā)物理機(jī)遇到的并發(fā)問(wèn)題其實(shí)與虛擬機(jī)中的情況有很多相似之處,所以物理機(jī)對(duì)并發(fā)的處理方案對(duì)于虛擬機(jī)的實(shí)現(xiàn)也有比較大的參考意義
????“充分的利用計(jì)算機(jī)效能”和“讓計(jì)算機(jī)并發(fā)運(yùn)行多個(gè)任務(wù)”之間的關(guān)系,看上去是緊密相連的,但是實(shí)際上并沒(méi)有想象中的那么簡(jiǎn)單,這其中有一個(gè)重要的原因在于計(jì)算機(jī)的大多數(shù)任務(wù)不能僅僅只依靠處理器去完成,處理器至少需要和內(nèi)存打交道,比如讀取數(shù)據(jù)、保存結(jié)果等,而這一過(guò)程中的I/O是無(wú)法避免的。由于計(jì)算機(jī)的存儲(chǔ)設(shè)備與處理器的運(yùn)算速度有幾個(gè)數(shù)量級(jí)的差距,所以現(xiàn)代計(jì)算機(jī)系統(tǒng)都不得不加入一層讀寫(xiě)速度盡可能接近處理器運(yùn)算速度的高速緩存(Cache)來(lái)作為內(nèi)存與處理器之間的緩沖:將運(yùn)算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速進(jìn)行,當(dāng)運(yùn)算結(jié)束后再?gòu)木彺嫱交貎?nèi)存之中,這樣處理器就無(wú)須等待緩慢的內(nèi)存讀寫(xiě)了。 ????但是這樣這種情況也帶來(lái)了另一個(gè)問(wèn)題,“緩存一致性(Cache Coherence)”。 緩存一致性(重要概念) 多處理器中,每一個(gè)處理器都擁有自己的高速緩存,同時(shí)又共享同一主內(nèi)存,如下圖所示 
當(dāng)多個(gè)處理器對(duì)同一塊內(nèi)存區(qū)域進(jìn)行運(yùn)算的時(shí)候,將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,如果發(fā)生這種情況,那同步回到主內(nèi)存時(shí)以誰(shuí)的緩存數(shù)據(jù)為準(zhǔn)呢?為了解決一致性的問(wèn)題,需要各個(gè)處理器訪問(wèn)緩存時(shí)都遵循一些協(xié)議,在讀寫(xiě)時(shí)要根據(jù)協(xié)議來(lái)進(jìn)行操作,這類協(xié) 議有MSI、MESI(Illinois Protocol)、MOSI、Synapse、Firefly及Dragon Protocol等。在本中將會(huì)多次提到的“內(nèi)存模型”一詞,可以理解為在特定的操作協(xié)議下,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫(xiě)訪問(wèn)的過(guò)程抽象。不同架構(gòu)的物理機(jī)器可以擁有不一樣的內(nèi)存模型,而Java虛擬機(jī)也有自己的內(nèi)存模型,并且這里介紹的內(nèi)存訪問(wèn)操作與硬件的緩存訪問(wèn)操作具有很高的可比性。 除了增加高速緩存之外,為了使得處理器內(nèi)部的運(yùn)算單元能盡量被 充分利用,處理器可能會(huì)對(duì)輸入代碼進(jìn)行亂序執(zhí)行(Out-Of-Order Execution)優(yōu)化,處理器會(huì)在計(jì)算之后將亂序執(zhí)行的結(jié)果重組,保證該 結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中各個(gè)語(yǔ)句計(jì)算的先 后順序與輸入代碼中的順序一致,因此,如果存在一個(gè)計(jì)算任務(wù)依賴另 外一個(gè)計(jì)算任務(wù)的中間結(jié)果,那么其順序性并不能靠代碼的先后順序來(lái) 保證。與處理器的亂序執(zhí)行優(yōu)化類似,Java虛擬機(jī)的即時(shí)編譯器中也有 類似的指令重排序(Instruction Reorder)優(yōu)化。 二、Java內(nèi)存模型Java虛擬機(jī)規(guī)范中試圖定義一種Java內(nèi)存模型(Java Memory Model,JMM)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓 Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。在此之前,主流程序語(yǔ)言(如C/C 等)直接使用物理硬件和操作系統(tǒng)的內(nèi)存模型,因此,會(huì)由于不同平臺(tái)上內(nèi)存模型的差異,有可能導(dǎo)致程序在一套平臺(tái)上 并發(fā)完全正常,而在另外一套平臺(tái)上并發(fā)訪問(wèn)卻經(jīng)常出錯(cuò),因此在某些場(chǎng)景就必須針對(duì)不同的平臺(tái)來(lái)編寫(xiě)程序。 定義Java內(nèi)存模型并非一件容易的事情,這個(gè)模型必須定義得足夠嚴(yán)謹(jǐn),才能讓Java的并發(fā)內(nèi)存訪問(wèn)操作不會(huì)產(chǎn)生歧義;但是,也必須定義得足夠?qū)捤?,使得虛擬機(jī)的實(shí)現(xiàn)有足夠的自由空間去利用硬件的各種 特性(寄存器、高速緩存和指令集中某些特有的指令)來(lái)獲取更好的執(zhí) 行速度。經(jīng)過(guò)長(zhǎng)時(shí)間的驗(yàn)證和修補(bǔ),在JDK 1.5(實(shí)現(xiàn)了JSR-133)發(fā)布后,Java內(nèi)存模型已經(jīng)成熟和完善起來(lái)了。 1.主內(nèi)存和工作內(nèi)存Java內(nèi)存模型主要是定義變量的訪問(wèn)規(guī)則,通俗的來(lái)講就是Java虛擬機(jī)對(duì)變量的存、取操作的底層細(xì)節(jié)。這里所說(shuō)的變量指的是靜態(tài)變量、常量、構(gòu)成數(shù)組的元素、實(shí)例字段,不包含局部變量和方法參數(shù),兩者在Java虛擬機(jī)中屬于線程私有,不會(huì)存在線程安全問(wèn)題。 Java虛擬機(jī)為了保持程序執(zhí)行的高效率,在定義內(nèi)存模型的同時(shí)并沒(méi)有限制執(zhí)行引擎使用處理器特定的寄存器或緩存來(lái)和主內(nèi)存交互,也沒(méi)有限制即時(shí)編譯器對(duì)代碼執(zhí)行順序進(jìn)行調(diào)整。 Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存(Main Memory) 中(此處的主內(nèi)存與介紹物理硬件時(shí)的主內(nèi)存名字一樣,兩者也可以互相類比,但此處僅是虛擬機(jī)內(nèi)存的一部分)。每條線程還有自己的工作內(nèi)存(Working Memory,可與前面講的處理器高速緩存類比),線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程 對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作 內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成,線程、 主內(nèi)存、工作內(nèi)存三者的交互關(guān)系如下圖所示。 
注意: Java內(nèi)存模型與內(nèi)存結(jié)構(gòu)不能混為一談,內(nèi)存結(jié)構(gòu)是指Java堆、棧、方法區(qū)等 2.JVM的內(nèi)存操作Java內(nèi)存模型定義了以下8種操作,虛擬機(jī)實(shí)現(xiàn)時(shí)需要保證以下操作均是原子的,不可再分的(double和long類型在32位和64位平臺(tái)上有一定區(qū)別需要特別留意) lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。 unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定。 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ī)遇到一個(gè)給變量賦值的字節(jié) 碼指令時(shí)執(zhí)行這個(gè)操作。 store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。 write(寫(xiě)入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。
如果要把一個(gè)變量從主內(nèi)存復(fù)制到工作內(nèi)存,那就要順序地執(zhí)行 read和load操作,如果要把變量從工作內(nèi)存同步回主內(nèi)存,就要順序地 執(zhí)行store和write操作。注意,Java內(nèi)存模型只要求上述兩個(gè)操作必須按 順序執(zhí)行,而沒(méi)有保證是連續(xù)執(zhí)行。也就是說(shuō),read與load之間、store 與write之間是可插入其他指令的,如對(duì)主內(nèi)存中的變量a、b進(jìn)行訪問(wèn) 時(shí),一種可能出現(xiàn)順序是read a、read b、load b、load a。除此之外, Java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時(shí)必須滿足如下規(guī)則: 不允許read和load、store和write操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者從工作內(nèi)存發(fā)起回寫(xiě)了但主內(nèi)存不接受的情況出現(xiàn)。 不允許一個(gè)線程丟棄它的最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。 不允許一個(gè)線程無(wú)原因地(沒(méi)有發(fā)生過(guò)任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。 一個(gè)新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量,換句話說(shuō),就是對(duì)一個(gè)變 量實(shí)施use、store操作之前,必須先執(zhí)行過(guò)了assign和load操作。 一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同 次數(shù)的unlock操作,變量才會(huì)被解鎖。 如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行l(wèi)oad或assign操作初始 化變量的值。 如果一個(gè)變量事先沒(méi)有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作,也不允許去unlock一個(gè)被其他線程鎖定住的變量。 對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)。
3.特殊的volatile類型Java虛擬機(jī)為volatile類型定義了一些特有的規(guī)則,當(dāng)一個(gè)變量定義為volatile之后,它將具備兩種特性,第一是保證此變量對(duì)所有線程的可見(jiàn)性,這里的“可見(jiàn)性”是指當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程來(lái)說(shuō)是可以立即得知的。而普通變量不能做到這一點(diǎn),普通變量的值在線程間傳遞均需要通過(guò)主內(nèi)存來(lái)完成,例如,線程A修改一個(gè)普通變量的值,然后向主內(nèi)存進(jìn)行回寫(xiě),另外一條 線程B在線程A回寫(xiě)完成了之后再?gòu)闹鲀?nèi)存進(jìn)行讀取操作,新變量值才會(huì)對(duì)線程B可見(jiàn)。 同時(shí)volatile對(duì)于很多人而言存在很多誤解,其中有很多人認(rèn)為volatile修飾的變量對(duì)內(nèi)存是立即可見(jiàn)的所以自然而然的認(rèn)為是線程安全的 ,然而這個(gè)觀點(diǎn)是存在一定問(wèn)題的。volatile修飾的變量對(duì)所有線程可見(jiàn),但是變量本身的修改操作并不是原子性操作,這就導(dǎo)致volatile變量在多線程情況下并不一定是線程安全的,請(qǐng)看一下例子 import java.util.concurrent.CountDownLatch;
public class VolatileTest {
private static volatile int total;
private static final int THREAD_COUNT = 10;
private static void increase() {
total ;
}
public static void main(String[] args) throws InterruptedException {
total = 0;
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i ) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int a = 0; a < 100; a ) {
increase();
}
countDownLatch.countDown();
}
});
threads[i].start();
}
countDownLatch.await();
System.out.println(total);
}
}
這段代碼啟動(dòng)了10個(gè)線程,每個(gè)線程執(zhí)行100次累加,那么預(yù)期的輸出應(yīng)該為1000,但是當(dāng)你實(shí)際執(zhí)行之后會(huì)發(fā)現(xiàn)每次輸出的結(jié)果大多數(shù)是小于1000,且每次輸出的結(jié)果都不一樣。造成這個(gè)問(wèn)題的原因出現(xiàn)在total ,利用javap可以查看關(guān)鍵部分字節(jié)碼指令,如下: public static void increase();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field total:I
3: iconst_1
4: iadd
5: putstatic #2 // Field total:I
8: return
LineNumberTable:
line 10: 0
line 11: 8
volatile保證了getstatic操作獲取的值是正確的,但是在執(zhí)行iconst_1(棧指令,入棧),iadd過(guò)程中,其他線程有可能已經(jīng)計(jì)算完畢并且將數(shù)據(jù)更新到主內(nèi)存中,這樣就導(dǎo)致當(dāng)前線程所擁有的的數(shù)據(jù)變成了過(guò)期數(shù)據(jù) ,而后putstatic操作則會(huì)將過(guò)期數(shù)據(jù)更新至主內(nèi)存。當(dāng)所有線程執(zhí)行完畢后,total的值便會(huì)小于預(yù)期數(shù)。 由于volatile變量只能保證可見(jiàn)性,所以在實(shí)際運(yùn)用中還需要遵循以下規(guī)則: 不過(guò)在下面這種情況中,volatile變量就非常適合 public class VolatileDemo {
private static volatile boolean stop;
public static void main(String[] args) {
while (!stop) {
// do something
}
}
public static void stop(){
stop=true;
}
}
由于volatile變量對(duì)所有線程保持可見(jiàn)性,當(dāng)變量被賦值為true時(shí),就會(huì)停止循環(huán),而在賦值的過(guò)程中沒(méi)有出現(xiàn)非原子性操作,所以這種方法是可靠并且安全的。 4.long和double變量的特殊規(guī)則Java內(nèi)存模型要求lock、unlock、read、load、assign、use、store、 write這8個(gè)操作都具有原子性,但是對(duì)于64位的數(shù)據(jù)類型(long和 double),在模型中特別定義了一條相對(duì)寬松的規(guī)定:允許虛擬機(jī)將沒(méi)有被volatile修飾的64位數(shù)據(jù)的讀寫(xiě)操作劃分為兩次32位的操作來(lái)進(jìn)行, 即允許虛擬機(jī)實(shí)現(xiàn)選擇可以不保證64位數(shù)據(jù)類型的load、store、read和 write這4個(gè)操作的原子性,這點(diǎn)就是所謂的long和double的非原子性協(xié)定 (Nonatomic Treatment ofdouble and long Variables)。 如果有多個(gè)線程共享一個(gè)并未聲明為volatile的long或double類型的 變量,并且同時(shí)對(duì)它們進(jìn)行讀取和修改操作,那么某些線程可能會(huì)讀取 到一個(gè)既非原值,也不是其他線程修改值的代表了“半個(gè)變量”的數(shù)值。 不過(guò)這種讀取到“半個(gè)變量”的情況非常罕見(jiàn)(在目前商用Java虛擬機(jī)中不會(huì)出現(xiàn)),因?yàn)镴ava內(nèi)存模型雖然允許虛擬機(jī)不把long和double 變量的讀寫(xiě)實(shí)現(xiàn)成原子操作,但允許虛擬機(jī)選擇把這些操作實(shí)現(xiàn)為具有原子性的操作,而且還“強(qiáng)烈建議”虛擬機(jī)這樣實(shí)現(xiàn)。在實(shí)際開(kāi)發(fā)中,目前各種平臺(tái)下的商用虛擬機(jī)幾乎都選擇把64位數(shù)據(jù)的讀寫(xiě)操作作為原子操作來(lái)對(duì)待,因此我們?cè)诰帉?xiě)代碼時(shí)一般不需要把用到的long和double 變量專門聲明為volatile。 5.原子性、可見(jiàn)性與有序性Java內(nèi)存模型圍繞著并發(fā)過(guò)程中如何處理原子性、可見(jiàn)性和順序性這三個(gè)特征來(lái)設(shè)計(jì)的。 原子性由Java內(nèi)存模型來(lái)直接保證的原子性變量操作包括read、load、assign、use、store和write,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問(wèn)讀寫(xiě)是具備原子性的(例外就是long和double的非原子性協(xié)定,讀者只要知道這件事情就可以了,無(wú)須太過(guò)在意這些幾乎不會(huì)發(fā)生的例外情況)。 可見(jiàn)性可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的 值,其他線程能夠立即得知這個(gè)修改。上文在講解volatile變量的時(shí)候我們已詳細(xì)討論過(guò)這一點(diǎn)。Java內(nèi)存模型是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來(lái)實(shí)現(xiàn)可見(jiàn)性的,無(wú)論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區(qū)別是,volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。因此,可以說(shuō) volatile保證了多線程操作時(shí)變量的可見(jiàn)性,而普通變量則不能保證這一 點(diǎn)。 有序性Java內(nèi)存模型的有序性在前面講解volatile時(shí) 也詳細(xì)地討論過(guò)了,Java程序中天然的有序性可以總結(jié)為一句話:如果 在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一 個(gè)線程,所有的操作都是無(wú)序的。前半句是指“線程內(nèi)表現(xiàn)為串行的語(yǔ) 義”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排 序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。 6.并發(fā)先行原則如果Java內(nèi)存模型中所有的有序性都僅僅靠volatile和synchronized來(lái)完成,那么有一些操作將會(huì)變得很煩瑣,但是我們?cè)诰帉?xiě)Java并發(fā)代碼 的時(shí)候并沒(méi)有感覺(jué)到這一點(diǎn),這是因?yàn)镴ava語(yǔ)言中有一個(gè)“先行發(fā) 生”(happens-before)的原則。這個(gè)原則非常重要,它是判斷數(shù)據(jù)是否 存在競(jìng)爭(zhēng)、線程是否安全的主要依據(jù),依靠這個(gè)原則,我們可以通過(guò)幾條規(guī)則一攬子地解決并發(fā)環(huán)境下兩個(gè)操作之間是否可能存在沖突的所有問(wèn)題。 先行發(fā)生是Java內(nèi)存模 型中定義的兩項(xiàng)操作之間的偏序關(guān)系,如果說(shuō)操作A先行發(fā)生于操作 B,其實(shí)就是說(shuō)在發(fā)生操作B之前,操作A產(chǎn)生的影響能被操作B觀察到,“影響”包括修改了內(nèi)存中共享變量的值、發(fā)送了消息、調(diào)用了方法等??赏ㄟ^(guò)以下偽代碼理解: //以下操作在線程A中執(zhí)行
a=1;
//以下操作在線程B中執(zhí)行
b=a;
//以下操作在線程C中執(zhí)行
a=2;
假設(shè)線程A中的操作"a=1"先行發(fā)生于線程B的操作"b=a",那么可以 確定在線程B的操作執(zhí)行后,變量b的值一定等于1,得出這個(gè)結(jié)論的依 據(jù)有兩個(gè):一是根據(jù)先行發(fā)生原則,"a=1"的結(jié)果可以被觀察到;二是線 程C還沒(méi)“登場(chǎng)”,線程A操作結(jié)束之后沒(méi)有其他線程會(huì)修改變量a的值。 現(xiàn)在再來(lái)考慮線程C,我們依然保持線程A和線程B之間的先行發(fā)生關(guān)系,而線程C出現(xiàn)在線程A和線程B的操作之間,但是線程C與線程B沒(méi)有先行發(fā)生關(guān)系,那b的值會(huì)是多少呢?答案是不確定!1和2都有可能,因?yàn)榫€程C對(duì)變量a的影響可能會(huì)被線程B觀察到,也可能不會(huì),這時(shí)候線程B就存在讀取到過(guò)期數(shù)據(jù)的風(fēng)險(xiǎn),不具備線程安全性。 以下是Java內(nèi)存模型與生俱來(lái)的先行發(fā)生關(guān)系 : 程序次序規(guī)則(Program Order Rule):在一個(gè)線程內(nèi),按照程序代碼順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作。準(zhǔn)確地說(shuō), 應(yīng)該是控制流順序而不是程序代碼順序,因?yàn)橐紤]分支、循環(huán)等結(jié)構(gòu)。 管程鎖定規(guī)則(Monitor Lock Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。這里必須強(qiáng)調(diào)的是同一個(gè)鎖,而“后面”是 指時(shí)間上的先后順序。 volatile變量規(guī)則(Volatile Variable Rule):對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作,這里的“后面”同樣是指時(shí)間 上的先后順序。 線程啟動(dòng)規(guī)則(Thread Start Rule):Thread對(duì)象的start()方法先行 發(fā)生于此線程的每一個(gè)動(dòng)作。 線程終止規(guī)則(Thread Termination Rule):線程中的所有操作都先 行發(fā)生于對(duì)此線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束、 Thread.isAlive()的返回值等手段檢測(cè)到線程已經(jīng)終止執(zhí)行。 線程中斷規(guī)則(Thread Interruption Rule):對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生,可以通過(guò) Thread.interrupted()方法檢測(cè)到是否有中斷發(fā)生。 對(duì)象終結(jié)規(guī)則(Finalizer Rule):一個(gè)對(duì)象的初始化完成(構(gòu)造函 數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始。 傳遞性(Transitivity):如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論。
來(lái)源:https://www./content-3-633251.html
|