jvm(12)-java内存模型与线程
生活随笔
收集整理的這篇文章主要介紹了
jvm(12)-java内存模型与线程
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
【0】README
0.1)本文部分文字描述轉(zhuǎn)自“深入理解jvm”,旨在學(xué)習(xí)“java內(nèi)存模型與線程” 的基礎(chǔ)知識(shí);
【1】概述 1)并發(fā)處理的廣泛應(yīng)用是使得 Amdahl 定律代替摩爾定律稱(chēng)為計(jì)算機(jī)性能發(fā)展源動(dòng)力的根本原因; 2)Amdahl 定律:該定律通過(guò)系統(tǒng)中并行化與串行化的比重來(lái)描述多處理器系統(tǒng)能獲得的運(yùn)算加速能力; 3)摩爾定律:該定律用于描述處理器晶體管數(shù)量與運(yùn)行效率間的發(fā)展關(guān)系; Conclusion)這兩個(gè)定律的更替代表了近年來(lái)硬件發(fā)展從追求處理器頻率到追求多核心并行處理的發(fā)展過(guò)程;
【2】硬件的效率與一致性 1)高速緩存(干貨——引入高速緩存)
【3】java內(nèi)存模型 0)intro to java內(nèi)存模型:java虛擬機(jī)規(guī)范試圖定義一種java內(nèi)存模型(java memory model)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果; 【3.1】主內(nèi)存與工作內(nèi)存 1)java內(nèi)存模型的主要目標(biāo):定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細(xì)節(jié); 2)java內(nèi)存模型規(guī)定了:所有的變量都存儲(chǔ)在主內(nèi)存中。
【3.4】對(duì)于long 和 double 型變量的特殊規(guī)則 1)java內(nèi)存模型要求lock, unlock, read, load, assign, use,store,write這8個(gè)操作都具有原子性:?但對(duì)于64位的數(shù)據(jù)類(lèi)型(long和double),在模型中特別定義了一條相對(duì)寬松的規(guī)定:允許虛擬機(jī)將沒(méi)有被 volatile修飾的64位數(shù)據(jù)的讀寫(xiě)操作劃分為兩次32位的操作來(lái)進(jìn)行,即允許虛擬機(jī)實(shí)現(xiàn)選擇可以不保證64位數(shù)據(jù)類(lèi)型的load, store,read和write這4個(gè)操作的原子性,這點(diǎn)就是所謂的 long 和double 的非原子性協(xié)定; 2)這項(xiàng)寬松的規(guī)定所導(dǎo)致的problem:如果有多個(gè)線程共享一個(gè)并未聲明為 volatile的long 或 double類(lèi)型的變量,并且同時(shí)對(duì)它們進(jìn)行讀取和修改操作,那么某些線程可能會(huì)讀取到一個(gè)既非原值,也不是其他線程修改值的代表了半個(gè)變量的數(shù)值; 3)不過(guò)這種讀取到的“半個(gè)變量”的case非常罕見(jiàn):因?yàn)閖ava內(nèi)存模型雖然允許虛擬機(jī)不把long 和 double 變量的讀寫(xiě)實(shí)現(xiàn)成原子操作,但允許虛擬機(jī)選擇把 這些操作實(shí)現(xiàn)為具有原子性的操作,而且還強(qiáng)烈建議虛擬機(jī)這樣實(shí)現(xiàn);(干貨——不過(guò)這種讀取到的“半個(gè)變量”的case非常罕見(jiàn))
【3.5】原子性,可見(jiàn)性與有序性 0)intro: java內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性, 可見(jiàn)性和有序性這3個(gè)特征來(lái)建立的; 1)原子性:由于java內(nèi)存模型來(lái)直接保證的原子性變量操作包括 read,load,assign,use,store和write,我們大致認(rèn)為基本數(shù)據(jù)類(lèi)型的訪問(wèn)讀寫(xiě)數(shù)據(jù)是具備原子性的。
【1】概述 1)并發(fā)處理的廣泛應(yīng)用是使得 Amdahl 定律代替摩爾定律稱(chēng)為計(jì)算機(jī)性能發(fā)展源動(dòng)力的根本原因; 2)Amdahl 定律:該定律通過(guò)系統(tǒng)中并行化與串行化的比重來(lái)描述多處理器系統(tǒng)能獲得的運(yùn)算加速能力; 3)摩爾定律:該定律用于描述處理器晶體管數(shù)量與運(yùn)行效率間的發(fā)展關(guān)系; Conclusion)這兩個(gè)定律的更替代表了近年來(lái)硬件發(fā)展從追求處理器頻率到追求多核心并行處理的發(fā)展過(guò)程;
【2】硬件的效率與一致性 1)高速緩存(干貨——引入高速緩存)
- 1.1)problem:由于計(jì)算機(jī)的存儲(chǔ)設(shè)備與處理器的運(yùn)算速率有幾個(gè)數(shù)量級(jí)的差距;
- 1.2)solution:引入一層讀寫(xiě)速度盡可能接近處理器速度的高速緩存(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ě)了;
- 2.1)問(wèn)題描述(problem): 當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及到同一塊內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,那同步到內(nèi)存時(shí)以誰(shuí)的數(shù)據(jù)為準(zhǔn)呢?
- 2.2)solution: 需要各個(gè)處理器遵循一些協(xié)議,在讀寫(xiě)時(shí)要根據(jù)協(xié)議來(lái)進(jìn)行操作,這類(lèi)協(xié)議有 MSI, MESI,等。
- 2.3)內(nèi)存模型: 可以理解為 在特定的操作協(xié)議下,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫(xiě)訪問(wèn)的過(guò)程抽象;(干貨——java內(nèi)存模型定義)
【3】java內(nèi)存模型 0)intro to java內(nèi)存模型:java虛擬機(jī)規(guī)范試圖定義一種java內(nèi)存模型(java memory model)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果; 【3.1】主內(nèi)存與工作內(nèi)存 1)java內(nèi)存模型的主要目標(biāo):定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細(xì)節(jié); 2)java內(nèi)存模型規(guī)定了:所有的變量都存儲(chǔ)在主內(nèi)存中。
- 2.1)每條線程還有自己的工作內(nèi)存: 線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作(讀取,賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)內(nèi)存中的變量。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量;(干貨——每條線程還有自己的工作內(nèi)存,工作內(nèi)存定義)
- 2.2)線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成:線程、內(nèi)存、工作內(nèi)存三者關(guān)系如下所示:
- 3.1)如果硬要扯上關(guān)系,則:主內(nèi)存主要對(duì)應(yīng)于java堆中的對(duì)象實(shí)例數(shù)據(jù)部分,而工作內(nèi)存則對(duì)應(yīng)于虛擬機(jī)棧中的部分區(qū)域;
- 3.2)更低層次上說(shuō):主內(nèi)存就直接對(duì)應(yīng)于物理硬件的內(nèi)存,而為了獲得更好的運(yùn)行速度,虛擬機(jī)可能會(huì)讓工作內(nèi)存優(yōu)先存儲(chǔ)于寄存器和高速緩存中,因?yàn)槌绦蜻\(yùn)行時(shí)主要訪問(wèn)讀寫(xiě)的是工作內(nèi)存;
- o1)lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài);
- o2)unlock(解鎖):作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定;
- o3)read(讀取):作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load 動(dòng)作使用;
- o4)load(載入):作用于工作內(nèi)存的變量, 它把 read 操作從主內(nèi)存中得到的變量放入工作內(nèi)存的變量副本中;
- o5)use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作;
- o6)assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作;
- o7)store(存儲(chǔ)):作用于工作內(nèi)存的變量, 它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用;
- o8)write(寫(xiě)入):作用于主內(nèi)存的變量, 它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中;
- 2.1)把變量從主內(nèi)存復(fù)制到工作內(nèi)存:順序執(zhí)行read和load操作(目的地是工作內(nèi)存);
- 2.2)把變量從工作內(nèi)存同步回主內(nèi)存:順序執(zhí)行store和write操作(目的地是主內(nèi)存);
- Attention)java內(nèi)存模型只要求上述兩個(gè)操作必須按順序執(zhí)行,沒(méi)有保證是連續(xù)執(zhí)行;即read和 load 之間,store和 write之間可以插入其他指令;(干貨——java內(nèi)存模型只要求上述兩個(gè)操作必須按順序執(zhí)行,沒(méi)有保證是連續(xù)執(zhí)行,它們之間還可以插入其他指令)
- r1)不允許read和 load,store 和 write操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者從工作內(nèi)存發(fā)起回寫(xiě)了但主內(nèi)存不接受的情況出現(xiàn);
- r2)不允許一個(gè)線程丟棄它的最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存;
- r3)不允許一個(gè)線程無(wú)原因地(沒(méi)有發(fā)生過(guò)任何 assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中;
- r4)一個(gè)新變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化的變量,換句話說(shuō),對(duì)一個(gè)變量實(shí)施 use,store操作前,必須先執(zhí)行過(guò) assign 和 load 操作;
- r5)一個(gè)變量在同一個(gè)時(shí)刻只允許一個(gè)線程對(duì)其進(jìn)行l(wèi)ock 操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行 lock后,只有執(zhí)行相同次數(shù)的unlock 操作,變量才會(huì)被解鎖;
- r6)如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock 操作,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行 load 或 assign 操作初始化變量的值;
- r7)如果一個(gè)變量事先沒(méi)有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作,也不允許去 unlock 一個(gè)被其他線程鎖定住的變量;
- r8)對(duì)一個(gè)變量執(zhí)行unlock 變量前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store, write操作);
- Conclusion) 這8種內(nèi)存訪問(wèn)操作以及上述規(guī)則限定,再加上稍后介紹的對(duì) volatile 的一些特殊規(guī)定,就已經(jīng)完全確定了java 程序中哪些內(nèi)存訪問(wèn)操作在并發(fā)下是安全的;
- c1)保證此變量對(duì)所有線程的可見(jiàn)性,這里的可見(jiàn)性指:當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程來(lái)說(shuō)是可以立即得知的。而普通變量做不到這一點(diǎn),其在線程間傳遞需要通過(guò)主內(nèi)存來(lái)完成;(干貨——這就是為什么會(huì)出現(xiàn)數(shù)據(jù)的臟讀)
- c2)對(duì)于volatile變量的可見(jiàn)性,有一些誤解: volatile變量對(duì)所有線程都是可見(jiàn)的,對(duì)volatile變量所有的寫(xiě)操作都能立刻反應(yīng)到其他線程中,即,volatile變量在各個(gè)線程中是一致的,所有基于 volatile變量的運(yùn)算在并發(fā)下是安全的。上述語(yǔ)句中的錯(cuò)誤在于并不能得出“基于 volatile變量的運(yùn)算在并發(fā)下是安全的”這個(gè)結(jié)論。
- 2.1)看個(gè)荔枝:
- public class VolatileTest {public static volatile int race = 0;public static void increase() {race++;}public static final int THREADS_COUNT = 20;public static void main(String[] args) {Thread[] threads = new Thread[THREADS_COUNT];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 10000; j++) {increase();}}});threads[i].start();}// 等待所有累計(jì)線程都endingwhile(Thread.activeCount() > 1) {Thread.yield();}System.out.println(race);} }
- 對(duì)以上執(zhí)行結(jié)果的分析(Analysis):
- A1)以上代碼的正確輸出結(jié)果是20000, 而執(zhí)行的結(jié)果每次都不一樣,且都小于20000;
- A2)用javap 反編譯命令得到如下字節(jié)碼,發(fā)現(xiàn)increase()方法對(duì)應(yīng)4條字節(jié)碼指令(return指令不算):
- 對(duì)以上字節(jié)碼的分析(Analysis):
- A1)當(dāng) getstatic指令 把 race 的值取到操作棧頂時(shí),volatile關(guān)鍵字保證了 race的值在此時(shí)是正確的,但是在執(zhí)行 iconst_1, iadd 這些指令的時(shí)候,其他線程可能已經(jīng)把race 的值加大了,而在操作棧頂?shù)闹稻妥兂闪诉^(guò)期的數(shù)據(jù),所以 putstatic 指令執(zhí)行后就可能把較小的race 值同步回主內(nèi)存中;
- A2)客觀上說(shuō),在這里使用 字節(jié)碼來(lái)分析并發(fā)問(wèn)題,不是很?chē)?yán)謹(jǐn)。因?yàn)榧词咕幾g出來(lái)只有一條字節(jié)碼指令,也不意味著執(zhí)行這條指令就是一個(gè)原子操作。一條字節(jié)碼指令也可能會(huì)轉(zhuǎn)化成若干條本地機(jī)器碼指令,此處使用 -XX:+PrintAssembly 參數(shù)輸出反匯編來(lái)分析會(huì)更加嚴(yán)謹(jǐn);(干貨——因?yàn)榧词咕幾g出來(lái)只有一條字節(jié)碼指令,也不意味著執(zhí)行這條指令就是一個(gè)原子操作)
- scene1)運(yùn)算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值;
- scene2)變量不需要與其他的狀態(tài)變量共同參與不變約束;
- 如果定義initialized變量沒(méi)有使用volatile修飾:就可能會(huì)由于指令重排序的優(yōu)化,導(dǎo)致位于線程A 中最后一句代碼“initialized=true”被提前執(zhí)行;這樣在線程B中使用配置信息的代碼就可能出現(xiàn)錯(cuò)誤,而volatile關(guān)鍵字則可以避免此類(lèi)情況的發(fā)生;
【3.4】對(duì)于long 和 double 型變量的特殊規(guī)則 1)java內(nèi)存模型要求lock, unlock, read, load, assign, use,store,write這8個(gè)操作都具有原子性:?但對(duì)于64位的數(shù)據(jù)類(lèi)型(long和double),在模型中特別定義了一條相對(duì)寬松的規(guī)定:允許虛擬機(jī)將沒(méi)有被 volatile修飾的64位數(shù)據(jù)的讀寫(xiě)操作劃分為兩次32位的操作來(lái)進(jìn)行,即允許虛擬機(jī)實(shí)現(xiàn)選擇可以不保證64位數(shù)據(jù)類(lèi)型的load, store,read和write這4個(gè)操作的原子性,這點(diǎn)就是所謂的 long 和double 的非原子性協(xié)定; 2)這項(xiàng)寬松的規(guī)定所導(dǎo)致的problem:如果有多個(gè)線程共享一個(gè)并未聲明為 volatile的long 或 double類(lèi)型的變量,并且同時(shí)對(duì)它們進(jìn)行讀取和修改操作,那么某些線程可能會(huì)讀取到一個(gè)既非原值,也不是其他線程修改值的代表了半個(gè)變量的數(shù)值; 3)不過(guò)這種讀取到的“半個(gè)變量”的case非常罕見(jiàn):因?yàn)閖ava內(nèi)存模型雖然允許虛擬機(jī)不把long 和 double 變量的讀寫(xiě)實(shí)現(xiàn)成原子操作,但允許虛擬機(jī)選擇把 這些操作實(shí)現(xiàn)為具有原子性的操作,而且還強(qiáng)烈建議虛擬機(jī)這樣實(shí)現(xiàn);(干貨——不過(guò)這種讀取到的“半個(gè)變量”的case非常罕見(jiàn))
【3.5】原子性,可見(jiàn)性與有序性 0)intro: java內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性, 可見(jiàn)性和有序性這3個(gè)特征來(lái)建立的; 1)原子性:由于java內(nèi)存模型來(lái)直接保證的原子性變量操作包括 read,load,assign,use,store和write,我們大致認(rèn)為基本數(shù)據(jù)類(lèi)型的訪問(wèn)讀寫(xiě)數(shù)據(jù)是具備原子性的。
- 1.1)同步塊——synchronized關(guān)鍵字:如果應(yīng)用場(chǎng)景需要一個(gè)更大范圍的原子性保證,java內(nèi)存模型還提供了lock 和 unlock 操作來(lái)滿足這些需求,盡管虛擬機(jī)沒(méi)有把lock 和 unlock 操作直接開(kāi)放給用戶使用,但是卻提供了更高層次的字節(jié)碼指令 monitorenter 和 monitorexit 來(lái)隱式地使用這兩個(gè)操作;
- 1.2)這兩個(gè)字節(jié)碼指令反映到j(luò)ava代碼中就是同步塊——synchronized關(guān)鍵字:因此在synchronized塊之間的操作也具備原子性;
- 2.1)java內(nèi)存模型是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來(lái)實(shí)現(xiàn)可見(jiàn)性的,無(wú)論是普通變量還是volatile變量都是如此;
- 2.2)普通變量與 volatile變量的區(qū)別是:volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新;所以volatile保證了多線程操作時(shí)變量的可見(jiàn)性,普通變量則不能保證這一點(diǎn);
- 2.3)java還有兩個(gè)關(guān)鍵字實(shí)現(xiàn)可見(jiàn)性: synchronized 和 final;
- 2.3.1)同步塊的可見(jiàn)性: 是由對(duì)一個(gè)變量執(zhí)行unlock 操作前,必須先把此變量同步回主內(nèi)存中;
- 2.3.2)而final關(guān)鍵字的可見(jiàn)性:被final修飾的字段在構(gòu)造器中一旦初始化完成,并且構(gòu)造器沒(méi)有把this 的引用傳遞出去,那在其他線程中就能看見(jiàn)final 字段的值。
- 2.3.3)看個(gè)荔枝:
- // final 可見(jiàn)性測(cè)試 public class FinalVisibilityTest {public static final int i;public final int j;static {i = 0;// do sth}{// 也可以選擇在構(gòu)造函數(shù)中初始化j = 0;// do sth} }
- 對(duì)以上代碼的分析(Analysis):變量i 和 j 都具備可見(jiàn)性,它們無(wú)須同步就能被其他線程正確訪問(wèn);
- 3.1)java程序中天然的有序性總結(jié)為一句話:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無(wú)序的。前半句是指: 線程內(nèi)表現(xiàn)為串行的語(yǔ)義,后半句是指:指令重排序現(xiàn)象和工作內(nèi)存與主內(nèi)存同步延遲現(xiàn)象;
- 3.2)volatile和 synchronized關(guān)鍵字保證了線程間操作的有序性:volatile關(guān)鍵字本身就包含了禁止指令重排序的語(yǔ)義,而synchronized則是由 一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock 操作這條規(guī)則獲得的,這條規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入;
- 3.1)程序次序規(guī)則:在一個(gè)線程內(nèi),按照程序代碼順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作;準(zhǔn)確地說(shuō),應(yīng)該是控制流順序;
- 3.2)管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作;這里必須強(qiáng)調(diào)的是同一個(gè)鎖,而后面是指時(shí)間上的先后順序;
- 3.3)volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作,這里的后面是指時(shí)間上的先后順序;
- 3.4)線程啟動(dòng)規(guī)則:Thread對(duì)象的start() 方法先行發(fā)生于此線程的每一個(gè)動(dòng)作;
- 3.5)線程終止規(guī)則:線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè),可以通過(guò)Thread.join() 方法結(jié)束,Thread.isAlive() 的返回值等手段檢測(cè)到線程已經(jīng)終止運(yùn)行;
- 3.6)線程中斷規(guī)則:對(duì)線程interrupt() 方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生,可以通過(guò) Thread.interrrupted() 方法檢測(cè)到是否有中斷發(fā)生;
- 3.7)對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于它的finalize() 方法的開(kāi)始;
- 3.8)傳遞性:如果操作A 先行發(fā)生于操作B, 操作B 先行發(fā)生于操作C,那就可以得出操作A 先行發(fā)生于 操作C的結(jié)論;
- A1)problem:假設(shè)線程A 先調(diào)用了 setValue(1), 之后線程B 調(diào)用了同一個(gè)對(duì)象的getValue() ,那么線程B 收到的value是什么?
- A2)可以判定:盡管線程A在操作時(shí)間上先于線程B, 但是無(wú)法確定線程B 中“getValue()” 方法的返回結(jié)果,換句話說(shuō),這里面的操作不是線程安全的;
- A3)solution:我們至少有兩種簡(jiǎn)單的解決方案:
- solution1)要么把getter 和 ?setter方法都定義為 synchronized方法,這樣就可以套用管程鎖定規(guī)則;
- solution2)要么把value定義為 volatile變量,由于setter方法對(duì)value的修改不依賴于 value的原值,滿足volatile關(guān)鍵字使用場(chǎng)景,這樣就可以套用volatile變量規(guī)則來(lái)實(shí)現(xiàn)先行發(fā)生關(guān)系;
- A4)得出結(jié)論:一個(gè)操作時(shí)間上的先發(fā)生 不代表這個(gè)操作會(huì)是先行發(fā)生,那如果一個(gè)操作先行發(fā)生是否就能推導(dǎo)出這個(gè)操作必定是 時(shí)間上的先行發(fā)生呢? (顯然推導(dǎo)不出來(lái))。一個(gè)典型的荔枝就是多次提到的“指令重排序”。
- A4.1)看個(gè)荔枝:
- // 以下操作在同一個(gè)線程中執(zhí)行 int i = 1; int j = 2;
- 對(duì)上述代碼的分析(Analysis): 根據(jù)程序次序規(guī)則, int i = 1 的操作先行發(fā)生于 int j =2,但 int j = 2 完全可能先被處理器執(zhí)行,這并不影響先行發(fā)生原則的正確性;
- Conclusion)以上兩個(gè)實(shí)例得出結(jié)論:時(shí)間先后順序與先行發(fā)生原則之間基本沒(méi)有太大的關(guān)系,所以我們衡量并發(fā)安全問(wèn)題的時(shí)候不要受到時(shí)間順序的干擾,一切必須以先行發(fā)生原則為準(zhǔn);
- 0.1)線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單位: 線程的引入,可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開(kāi),各個(gè)線程既可以共享進(jìn)程資源(內(nèi)存地址,文件IO等),又可以獨(dú)立調(diào)度(線程是CPU 調(diào)度的基本單位);
- 0.2)線程實(shí)現(xiàn)的3種方式:使用內(nèi)核線程實(shí)現(xiàn),使用用戶線程實(shí)現(xiàn),使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)(干貨——線程實(shí)現(xiàn)的3種方式:使用內(nèi)核線程實(shí)現(xiàn)+使用用戶線程實(shí)現(xiàn)+使用用戶線程和輕量級(jí)進(jìn)程混合實(shí)現(xiàn))
- 1.1)內(nèi)核線程(KLT,Kernel-Level Thread):就是直接由操作系統(tǒng)內(nèi)核(下稱(chēng)內(nèi)核)支持的線程,這種線程由內(nèi)核來(lái)完成線程切換,內(nèi)核通過(guò)操縱調(diào)度器對(duì)線程進(jìn)行調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上。(干貨——內(nèi)核線程和輕量級(jí)進(jìn)程的定義)
- 1.2)程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Light Weight Process, LWP):輕量級(jí)進(jìn)程就是我們通常意義上講的線程,由于每個(gè)輕量級(jí)線程都由一個(gè)內(nèi)核線程支持,因此只有先支持內(nèi)核線程,才能有輕量級(jí)進(jìn)程。這種輕量級(jí)進(jìn)程與內(nèi)核線程間1:1 的關(guān)系稱(chēng)為一對(duì)一的線程模型,如下圖所示:(干貨——引入輕量級(jí)進(jìn)程)
- 1.3)輕量級(jí)進(jìn)程有局限性:
- 1.3.1)首先:由于是基于內(nèi)核線程實(shí)現(xiàn)的,所以各種線程操作,如創(chuàng)建,析構(gòu)及同步,都需要進(jìn)行系統(tǒng)調(diào)用, 而系統(tǒng)調(diào)用的代價(jià)相對(duì)較高,需要在用戶態(tài)和內(nèi)核態(tài)中來(lái)回切換;
- 1.3.2)其次:每個(gè)輕量級(jí)進(jìn)程都需要有一個(gè)內(nèi)存線程的支持,因此輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的棧空間),因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的;
- 3.1)在該實(shí)現(xiàn)方式下,既存在用戶線程,也存在輕量級(jí)進(jìn)程;
- 3.2)用戶線程還是完全建立在用戶空間中:因此用戶線程的創(chuàng)建,切換,析構(gòu)等操作依然廉價(jià),并且可以支持大規(guī)模的用戶線程并發(fā);
- 3.3)操作系統(tǒng)提供支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁:這樣可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過(guò)輕量級(jí)線程來(lái)完成,大大降低了整個(gè)進(jìn)程被完全阻塞 的風(fēng)險(xiǎn);(干貨——操作系統(tǒng)提供支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁)
- 3.4)在這種混合模式中:用戶線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,即為 N:M 的關(guān)系,如下圖所示, 這種就是多對(duì)多的線程模型;
- 2.1)其好處是:實(shí)現(xiàn)簡(jiǎn)單,而且由于線程要把自己的事情干完后才會(huì)進(jìn)行線程切換,切換操作讀線程自己是可知的,所以沒(méi)有什么線程同步的問(wèn)題;
- 2.2)其壞處是:線程執(zhí)行時(shí)間不可控制,甚至如果一個(gè)線程編寫(xiě)有問(wèn)題,一直不告訴系統(tǒng)進(jìn)行線程切換,那么程序就會(huì)一直阻塞在那里;
- 3.1)java使用的方式就是 搶占式線程調(diào)度方式;
- 3.2)雖然java 線程調(diào)度是系統(tǒng)自動(dòng)完成的: 但我們還是可以建議系統(tǒng)給某些線程多分配一點(diǎn)執(zhí)行時(shí)間,另外一些線程則可以少分配一點(diǎn)——這項(xiàng)操作可以通過(guò)設(shè)置線程優(yōu)先級(jí)來(lái)完成;(干貨——設(shè)置java 線程優(yōu)先級(jí))
- 3.3.)不過(guò)線程優(yōu)先級(jí)并不是太靠譜:因?yàn)閖ava的線程是通過(guò)映射到系統(tǒng)的原生線程上來(lái)實(shí)現(xiàn)的,所以線程調(diào)度最終還是取決于 操作系統(tǒng);雖然現(xiàn)在很多os 都提供了線程優(yōu)先級(jí),但不見(jiàn)得和 能與 java線程的優(yōu)先級(jí)一一對(duì)應(yīng);如 Solaris中有 2^32 種優(yōu)先級(jí),而windows只有7種 ;(干貨——java的線程優(yōu)先級(jí)并不是太靠譜)
- 3.4)下表顯示了 java線程優(yōu)先級(jí) 與 windows 線程優(yōu)先級(jí)之間的對(duì)應(yīng)關(guān)系:
- C1)上文說(shuō)到的“java線程優(yōu)先級(jí)并不是太靠譜”,不僅僅是在說(shuō)一些平臺(tái)上不同的優(yōu)先級(jí)實(shí)際會(huì)變得相同這一點(diǎn),還有其他case 讓我們不能太依賴優(yōu)先級(jí):優(yōu)先級(jí)可能會(huì)被系統(tǒng)自行改變。(干貨——優(yōu)先級(jí)可能會(huì)被系統(tǒng)自行改變)
- C2)如,在windows 中存在一個(gè)稱(chēng)為 “優(yōu)先級(jí)推進(jìn)器”的功能,作用是 當(dāng)系統(tǒng)發(fā)現(xiàn)一個(gè)線程執(zhí)行得特別勤奮的話,可能會(huì)越過(guò)線程優(yōu)先級(jí)去為它分配執(zhí)行時(shí)間;
- 1.1)新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這個(gè)狀態(tài);
- 1.2)運(yùn)行(Runnable):Runable包括了os 線程狀態(tài)中的 Running 和 Ready,也就是處于 此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU 為它分配執(zhí)行時(shí)間;
- 1.3)無(wú)限期等待(Waiting):處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,它們要等待被其他線程顯式的喚醒。以下方法會(huì)讓線程陷入無(wú)限期的等待狀態(tài)(methods):
- m1)沒(méi)有設(shè)置Timeout參數(shù)的Object.wait()方法;
- m2)沒(méi)有設(shè)置Timeout參數(shù)的 Thread.join() 方法;
- m3)LockSupport.park() 方法;
- 1.4)限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會(huì)被分配CPU 執(zhí)行時(shí)間,不過(guò)無(wú)需等待被其他線程顯式喚醒,在一定時(shí)間之后,它們會(huì)由系統(tǒng)自動(dòng)喚醒。以下方法會(huì)讓線程進(jìn)入限期等待狀態(tài)(methods):
- m1)Thread.sleep() 方法;
- m2)設(shè)置了Timeout參數(shù)的Object.wait()方法;
- m3)設(shè)置了Timeout參數(shù)的 Thread.join() 方法;
- m4)LockSupport.parkNanos() 方法;
- m5)LockSupport.parkUntil() 方法;
- 1.5)阻塞(Blocked):線程被阻塞了,?阻塞狀態(tài)與等待狀態(tài)的區(qū)別是:阻塞狀態(tài)在等待著獲取到一個(gè)排他鎖,這個(gè)事件將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生;而等待狀態(tài)則是在等待一段時(shí)間,或者喚醒動(dòng)作的發(fā)生。在程序等待進(jìn)入同步區(qū)域的時(shí)候, 線程將進(jìn)入這種狀態(tài);
- 1.6)結(jié)束(Terminated):已經(jīng)終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行;
總結(jié)
以上是生活随笔為你收集整理的jvm(12)-java内存模型与线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ddos高防ip原理(ddos防ip原理
- 下一篇: 网站没有备案怎么申请广告(网站没有备案怎