1、主內(nèi)存與工作內(nèi)存
Java內(nèi)存模型的主要目的是定義程序中各種變量的訪問規(guī)則,即關(guān)注再虛擬機(jī)中把變量值存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量值這樣的底層細(xì)節(jié)。此處的變量(Variables)只包括實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,不包括局部變量與方法參數(shù)。
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存(Main Memory)中。每條線程還有自己的工作內(nèi)存(Working Memory),線程的工作內(nèi)存中保存了被該線程使用的變量的主內(nèi)存副本,線程對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的數(shù)據(jù)。不同的線程之間也無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成。線程、主內(nèi)存、工作內(nèi)存三者的交互關(guān)系如下圖所示:

2、內(nèi)存間交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存這一類的實(shí)現(xiàn)細(xì)節(jié),Java內(nèi)存模型中定義了以下8種操作來完成。Java虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的、不可再分的。
1)lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。
2)unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
3)read(讀?。鹤饔糜谥鲀?nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用。
4)load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
5)use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
6)assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
7)store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。
8)write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。
如果要把一個(gè)變量從主內(nèi)存拷貝到工作內(nèi)存,那就要按順序執(zhí)行read和load操作,如果要把變量從工作內(nèi)存同步回主內(nèi)存,就要按順序執(zhí)行store和write操作。除此之外,Java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時(shí)必須滿足如下規(guī)則:
1)不允許read和load、store和write操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況出現(xiàn)。
2)不允許一個(gè)線程丟棄它最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
3)不允許一個(gè)線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。
4)一個(gè)新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量,換句話說就是對(duì)一個(gè)變量實(shí)施use、store操作之前,必須先執(zhí)行assign和load操作。
5)一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖。
6)如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行l(wèi)oad或assign操作以初始化變量的值。
7)如果一個(gè)變量事先沒有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作,也不允許去unlock一個(gè)被其他線程鎖定的變量。
8)對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)。
3、對(duì)于volatile型變量的特殊規(guī)則
關(guān)鍵字volatile可以說時(shí)Java虛擬機(jī)提供的最輕量級(jí)的同步機(jī)制,但是它并不容易被正確、完整地理解,以至于許多程序員都習(xí)慣去避免使用它,遇到需要處理多線程數(shù)據(jù)競爭問題地時(shí)候一律使用synchronized來進(jìn)行同步。
Java內(nèi)存模型為volatile專門定義了一些特殊地訪問規(guī)則。假定T表示一個(gè)線程,V和W分別表示兩個(gè)volatile型變量,那么在進(jìn)行read、load、use、assign、store和write操作時(shí)需要滿足如下規(guī)則:
1)只有當(dāng)線程T對(duì)變量V執(zhí)行地前一個(gè)動(dòng)作是load的時(shí)候,線程T才能對(duì)變量V執(zhí)行use動(dòng)作;并且,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是use的時(shí)候,線程T才能對(duì)變量V執(zhí)行l(wèi)oad動(dòng)作。線程T對(duì)變量V的use動(dòng)作可以認(rèn)為是和線程T對(duì)變量V的load、read動(dòng)作相關(guān)聯(lián)的,必須連續(xù)且一起出現(xiàn)。
這條規(guī)則要求在工作內(nèi)存中,每次使用V前都必須先從主內(nèi)存刷新最新的值,用于保證能看見其他線程對(duì)變量V所做的修改。
2)只有當(dāng)線程T對(duì)變量V執(zhí)行的前一個(gè)動(dòng)作是assign的時(shí)候,線程T才能對(duì)變量V執(zhí)行store動(dòng)作;并且,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是store的時(shí)候,線程T才能對(duì)變量V執(zhí)行assign動(dòng)作。線程T對(duì)變量V的assign動(dòng)作可以認(rèn)為是和線程T對(duì)變量V的store、write動(dòng)作相關(guān)聯(lián)的,必須連續(xù)且一起出現(xiàn)。
這條規(guī)則要求在工作內(nèi)存中,每次修改V后都必須立刻同步回主內(nèi)存中,用于保證其他線程可以看到自己對(duì)變量V所做的修改。
3)假定動(dòng)作A是線程T對(duì)變量V實(shí)施的use或assign動(dòng)作,假定動(dòng)作F是和動(dòng)作A相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作P是和動(dòng)作F相應(yīng)的對(duì)變量V的read或write動(dòng)作;與此類似,假定動(dòng)作B是線程T對(duì)變量W實(shí)施的use或assign動(dòng)作,假定動(dòng)作G是和動(dòng)作B相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作Q是和動(dòng)作G相應(yīng)的對(duì)變量W的read或write動(dòng)作。如果A先于B,那么P先于Q。
這條規(guī)則要求volatile修飾的變量不能被指令重排序優(yōu)化,從而保證代碼的執(zhí)行順序與程序的順序相同。
簡單來講就是:
1)volatile關(guān)鍵字保證變量對(duì)所有線程的可見性。
2)volatile關(guān)鍵字禁止指令重排。
3)volatile關(guān)鍵字不能保證變量的原子性。
注:不保證原子性的原因是從主內(nèi)存讀取到寫回主內(nèi)存這段時(shí)間內(nèi)變量是存在工作內(nèi)存可能在被線程操作。
保證原子性可以使用synchronized、java.util.concurrent中的鎖或原子類。
4、原子性、可見性與有序性
1)原子性
由Java內(nèi)存模型來直接保證的原子性變量操作包括read、load、assign、use、store和write這六個(gè), 我們大致可以認(rèn)為,基本數(shù)據(jù)類型的訪問、讀寫都是具備原子性的。
如果應(yīng)用場景需要一個(gè)更大范圍的原子性保證,Java內(nèi)存模型還提供了lock和unlock操作來滿足這種需求,盡管虛擬機(jī)未把lock和unlock操作直接開放給用戶使用,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式地使用這兩個(gè)操作。這兩個(gè)字節(jié)碼指令反映到Java代碼中就是同步塊——synchronized關(guān)鍵字,因此在synchronized塊之間的操作也具備原子性。
2)可見性
可見性就是指當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改。Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi) 存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實(shí)現(xiàn)可見性的,無論是 普通變量還是volatile變量都是如此。普通變量與volatile變量的區(qū)別是,volatile的特殊規(guī)則保證了新值 能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。因此我們可以說volatile保證了多線程操作 時(shí)變量的可見性,而普通變量則不能保證這一點(diǎn)。
除了volatile之外,Java還有兩個(gè)關(guān)鍵字能實(shí)現(xiàn)可見性,它們是synchronized和final。同步塊的可見性是由“對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)”這條規(guī)則獲得的。而final關(guān)鍵字的可見性是指:被final修飾的字段在構(gòu)造器中一旦被初始化完成,并且構(gòu)造器沒有把“this”的引用傳遞出去,那么在其他線程中就能看見final字段的值。
3)有序性
Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程, 所有的操作都是無序的。前半句是指“線程內(nèi)似表現(xiàn)為串行的語義”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。
Java語言提供了volatile和synchronized兩個(gè)關(guān)鍵字來保證線程之間操作的有序性,volatile關(guān)鍵字本 身就包含了禁止指令重排序的語義,而synchronized則是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì) 其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的,這個(gè)規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入。
5、先行發(fā)生原則
Java內(nèi)存模型中存在一些”天然的“先行發(fā)生關(guān)系,這些先行發(fā)生關(guān)系無須任何同步器協(xié)助就已經(jīng)存在,可以在編碼中直接使用。如果兩個(gè)操作之間的關(guān)系不在此列,并且無法從下列規(guī)則推導(dǎo)出來,則它們就沒有順序性保障,虛擬機(jī)可以對(duì)它們隨意地進(jìn)行重排序。
1)程序次序規(guī)則(Program Order Rule):在一個(gè)線程內(nèi),按照控制流順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作。注意,這里說的是控制流順序而不是程序代碼順序,因?yàn)橐紤]分支、循環(huán)等結(jié)構(gòu)。
2)管程鎖定規(guī)則(Monitor Lock Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。這里必須強(qiáng)調(diào)的是“同一個(gè)鎖”,而“后面”是指時(shí)間上的先后。
3)volatile變量規(guī)則(Volatile Variable Rule):對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作,這里的“后面”同樣是指時(shí)間上的先后。
4)線程啟動(dòng)規(guī)則(Thread Start Rule):Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。
5)線程終止規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測,我們可以通過Thread::join()方法是否結(jié)束、Thread::isAlive()的返回值等手段檢測線程是否已經(jīng)終止執(zhí)行。
6)線程中斷規(guī)則(Thread Interruption Rule):對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生,可以通過Thread::interrupted()方法檢測到是否有中斷發(fā)生。
7)對(duì)象終結(jié)規(guī)則(Finalizer Rule):一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開始。
8)傳遞性(Transitivity):如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論。