1鬓梅、概述
Java程序是需要運(yùn)行在Java虛擬機(jī)上面的谋作,Java內(nèi)存模型(Java Memory Model ,JMM)就是一種符合內(nèi)存模型規(guī)范的柏肪,屏蔽了各種硬件和操作系統(tǒng)的訪問差異的,保證了Java程序在各種平臺(tái)下對內(nèi)存的訪問都能保證效果一致的機(jī)制及規(guī)范余掖。
提到Java內(nèi)存模型鲫凶,一般指的是JDK 5 開始使用的新的內(nèi)存模型禀崖,主要由JSR-133: JavaTM Memory Model and Thread Specification 描述。感興趣的可以參看下這份PDF文檔(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf)
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中螟炫,每條線程還有自己的工作內(nèi)存帆焕,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行不恭,而不能直接讀寫主內(nèi)存。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量财饥,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行换吧。
而JMM就作用于工作內(nèi)存和主存之間數(shù)據(jù)同步過程。他規(guī)定了如何做數(shù)據(jù)同步以及什么時(shí)候做數(shù)據(jù)同步钥星。
這里面提到的主內(nèi)存和工作內(nèi)存沾瓦,讀者可以簡單的類比成計(jì)算機(jī)內(nèi)存模型中的主存和緩存的概念。特別需要注意的是谦炒,主內(nèi)存和工作內(nèi)存與JVM內(nèi)存結(jié)構(gòu)中的Java堆贯莺、棧、方法區(qū)等并不是同一個(gè)層次的內(nèi)存劃分宁改,無法直接類比缕探。《深入理解Java虛擬機(jī)》中認(rèn)為还蹲,如果一定要勉強(qiáng)對應(yīng)起來的話爹耗,從變量耙考、主內(nèi)存、工作內(nèi)存的定義來看潭兽,主內(nèi)存主要對應(yīng)于Java堆中的對象實(shí)例數(shù)據(jù)部分倦始。工作內(nèi)存則對應(yīng)于虛擬機(jī)棧中的部分區(qū)域。
所以山卦,再來總結(jié)下鞋邑,JMM是一種規(guī)范,目的是解決由于多線程通過共享內(nèi)存進(jìn)行通信時(shí)账蓉,存在的本地內(nèi)存數(shù)據(jù)不一致枚碗、編譯器會(huì)對代碼指令重排序、處理器會(huì)對代碼亂序執(zhí)行等帶來的問題剔猿。
2视译、實(shí)現(xiàn)
了解Java多線程的朋友都知道,在Java中提供了一系列和并發(fā)處理相關(guān)的關(guān)鍵字归敬,比如volatile酷含、synchronized、final汪茧、concurrent包等椅亚。其實(shí)這些就是Java內(nèi)存模型封裝了底層的實(shí)現(xiàn)后提供給程序員使用的一些關(guān)鍵字。
在開發(fā)多線程的代碼的時(shí)候舱污,我們可以直接使用synchronized等關(guān)鍵字來控制并發(fā)呀舔,從來就不需要關(guān)心底層的編譯器優(yōu)化、緩存一致性等問題扩灯。所以媚赖,Java內(nèi)存模型,除了定義了一套規(guī)范珠插,還提供了一系列原語惧磺,封裝了底層實(shí)現(xiàn)后,供開發(fā)者直接使用捻撑。
本文并不準(zhǔn)備把所有的關(guān)鍵字逐一介紹其用法磨隘,因?yàn)殛P(guān)于各個(gè)關(guān)鍵字的用法,網(wǎng)上有很多資料顾患。讀者可以自行學(xué)習(xí)番捂。本文還有一個(gè)重點(diǎn)要介紹的就是,我們前面提到江解,并發(fā)編程要解決原子性设预、有序性和一致性的問題,我們就再來看下膘流,在Java中絮缅,分別使用什么方式來保證鲁沥。
Java內(nèi)存模型是圍繞著并發(fā)過程中如何處理原子性、可見性和順序性這三個(gè)特征來設(shè)計(jì)的耕魄。
原子性
在Java中画恰,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的吸奴,要么執(zhí)行允扇,要么不執(zhí)行。
請分析以下哪些操作是原子性操作:
x =10; //語句1
y = x; //語句2
x++; //語句3
x = x +1; //語句4
咋一看则奥,有些朋友可能會(huì)說上面的4個(gè)語句中的操作都是原子性操作考润。其實(shí)只有語句1是原子性操作,其他三個(gè)語句都不是原子性操作读处。
- 語句1是直接將數(shù)值10賦值給x糊治,也就是說線程執(zhí)行這個(gè)語句的會(huì)直接將數(shù)值10寫入到工作內(nèi)存中。
- 語句2實(shí)際上包含2個(gè)操作罚舱,它先要去讀取x的值井辜,再將x的值寫入工作內(nèi)存,雖然讀取x的值以及 將x的值寫入工作內(nèi)存 這2個(gè)操作都是原子性操作管闷,但是合起來就不是原子性操作了粥脚。
- 同樣的,x++和 x = x+1包括3個(gè)操作:讀取x的值包个,進(jìn)行加1操作刷允,寫入新的值。
所以上面4個(gè)語句只有語句1的操作具備原子性碧囊。
也就是說树灶,只有簡單的讀取、賦值(而且必須是將值賦值給某個(gè)變量糯而,變量之間的相互賦值不是原子操作)才是原子操作破托。
從上面可以看出,Java內(nèi)存模型只保證了基本讀取和賦值是原子性操作歧蒋,如果要實(shí)現(xiàn)更大范圍操作的原子性,可以通過synchronized和Lock來實(shí)現(xiàn)州既。由于synchronized和Lock能夠保證任一時(shí)刻只有一個(gè)線程執(zhí)行該代碼塊谜洽,那么自然就不存在原子性問題了,從而保證了原子性
在Java中吴叶,為了保證原子性阐虚,提供了兩個(gè)高級的字節(jié)碼指令monitorenter
和monitorexit
。這兩個(gè)字節(jié)碼蚌卤,在Java中對應(yīng)的關(guān)鍵字就是synchronized
实束。
因此奥秆,在Java中可以使用synchronized
來保證方法和代碼塊內(nèi)的操作是原子性的。
可見性
可見性是指一個(gè)線程修改了一個(gè)變量的值后咸灿,其他線程立即可以感知到這個(gè)值的修改构订。
Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值的這種依賴主內(nèi)存作為傳遞媒介的方式來實(shí)現(xiàn)的避矢。
Java中的volatile
關(guān)鍵字提供了一個(gè)功能悼瘾,那就是被其修飾的變量在被修改后可以立即同步到主內(nèi)存,被其修飾的變量在每次是用之前都從主內(nèi)存刷新审胸。因此亥宿,可以使用volatile
來保證多線程操作時(shí)變量的可見性。
除了volatile
砂沛,Java中的synchronized
和Lock
兩個(gè)關(guān)鍵字也可以實(shí)現(xiàn)可見性烫扼。synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會(huì)將對變量的修改刷新到主存當(dāng)中碍庵。因此可以保證可見性映企。
有序性
有序性從不同的角度來看是不同的。單純單線程來看都是有序的怎抛,但到了多線程就會(huì)跟我們預(yù)想的不一樣卑吭。可以這么說:如果在本線程內(nèi)部觀察马绝,所有操作都是有序的豆赏;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無序的富稻。前半句說的就是“線程內(nèi)表現(xiàn)為串行的語義”掷邦,后半句指得是“指令重排序”現(xiàn)象和主內(nèi)存與工作內(nèi)存之間同步存在延遲的現(xiàn)象。
在Java內(nèi)存模型中椭赋,允許編譯器和處理器對指令進(jìn)行重排序抚岗,但是重排序過程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性哪怔。
在Java中宣蔚,可以使用synchronized
和volatile
來保證多線程之間操作的有序性。實(shí)現(xiàn)方式有所區(qū)別:
volatile
關(guān)鍵字會(huì)禁止指令重排认境。synchronized
關(guān)鍵字保證同一時(shí)刻只允許一條線程操作胚委。
總體來看,synchronized對三種特性都有支持叉信,雖然簡單亩冬,但是如果無控制的濫用對性能就會(huì)產(chǎn)生較大影響
3、happens-before 原則
Java內(nèi)存模型具備一些先天的“有序性”硼身,即不需要通過任何手段就能夠得到保證的有序性硅急,這個(gè)通常也稱為 happens-before 原則覆享。如果兩個(gè)操作的執(zhí)行次序無法從happens-before原則推導(dǎo)出來,那么它們就不能保證它們的有序性营袜,虛擬機(jī)可以隨意地對它們進(jìn)行重排序撒顿。
下面就來具體介紹下happens-before原則(先行發(fā)生原則):
- 1、程序次序規(guī)則:一個(gè)線程內(nèi)连茧,按照代碼順序核蘸,書寫在前面的操作先行發(fā)生于書寫在后面的操作
- 2、鎖定規(guī)則:一個(gè)unLock操作先行發(fā)生于后面對同一個(gè)鎖的lock操作
- 3啸驯、volatile變量規(guī)則:對一個(gè)變量的寫操作先行發(fā)生于后面對這個(gè)變量的讀操作
- 4客扎、傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C罚斗,則可以得出操作A先行發(fā)生于操作C
- 5徙鱼、線程啟動(dòng)規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個(gè)一個(gè)動(dòng)作
- 6、線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生
- 7针姿、線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測袱吆,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
- 8距淫、對象終結(jié)規(guī)則:一個(gè)對象的初始化完成先行發(fā)生于他的finalize()方法的開始
這8條原則摘自《深入理解Java虛擬機(jī)》绞绒。
這8條規(guī)則中,前4條規(guī)則是比較重要的榕暇,后4條規(guī)則都是顯而易見的蓬衡。
下面我們來解釋一下前4條規(guī)則:
對于程序次序規(guī)則來說,我的理解就是一段程序代碼的執(zhí)行在單個(gè)線程中看起來是有序的彤枢。注意狰晚,雖然這條規(guī)則中提到“書寫在前面的操作先行發(fā)生于書寫在后面的操作”,這個(gè)應(yīng)該是程序看起來執(zhí)行的順序是按照代碼順序執(zhí)行的缴啡,因?yàn)樘摂M機(jī)可能會(huì)對程序代碼進(jìn)行指令重排序壁晒。雖然進(jìn)行重排序,但是最終執(zhí)行的結(jié)果是與程序順序執(zhí)行的結(jié)果一致的业栅,它只會(huì)對不存在數(shù)據(jù)依賴性的指令進(jìn)行重排序秒咐。因此,在單個(gè)線程中碘裕,程序執(zhí)行看起來是有序執(zhí)行的反镇,這一點(diǎn)要注意理解。事實(shí)上娘汞,這個(gè)規(guī)則是用來保證程序在單線程中執(zhí)行結(jié)果的正確性,但無法保證程序在多線程中執(zhí)行的正確性夕玩。
第二條規(guī)則也比較容易理解你弦,也就是說無論在單線程中還是多線程中惊豺,同一個(gè)鎖如果出于被鎖定的狀態(tài),那么必須先對鎖進(jìn)行了釋放操作禽作,后面才能繼續(xù)進(jìn)行l(wèi)ock操作尸昧。
第三條規(guī)則是一條比較重要的規(guī)則,直觀地解釋就是旷偿,如果一個(gè)線程先去寫一個(gè)變量烹俗,然后一個(gè)線程去進(jìn)行讀取,那么寫入操作肯定會(huì)先行發(fā)生于讀操作萍程。
第四條規(guī)則實(shí)際上就是體現(xiàn)happens-before原則具備傳遞性幢妄。
4、volatile的原理和實(shí)現(xiàn)機(jī)制
前面講述了源于volatile關(guān)鍵字的一些使用茫负,下面我們來探討一下volatile到底如何保證可見性和禁止指令重排序的蕉鸳。
下面這段話摘自《深入理解Java虛擬機(jī)》:
“觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時(shí)所生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時(shí)忍法,會(huì)多出一個(gè)lock前綴指令”
lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄)潮尝,內(nèi)存屏障會(huì)提供3個(gè)功能:
1)它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面饿序;即在執(zhí)行到內(nèi)存屏障這句指令時(shí)勉失,在它前面的操作已經(jīng)全部完成;
2)當(dāng)寫一個(gè)volatile變量時(shí)原探,JMM會(huì)把該線程本地緩存中的共享變量刷新到主內(nèi)存乱凿;
3)當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對應(yīng)的本地緩存置無效踢匣,線程接下來將從主內(nèi)存中讀取共享變量告匠;
5、Java指令執(zhí)行的過程以及volatile修飾詞作用
Java指令執(zhí)行的過程:
- 1.將變量從主存復(fù)制到線程的工作內(nèi)存中离唬;
- 2.然后進(jìn)行讀操作后专;
- 3.有賦值指令時(shí)進(jìn)行賦值操作;
- 4.將結(jié)果寫入主存中输莺;
以上4步都是原子性的戚哎,但組合到一起,多線程操作時(shí)不能保證整體原子性嫂用,這也就是線程并發(fā)安全問題的原因型凳。
當(dāng)變量被volatile修飾以后:
- 1.某一線程對volatile修飾的變量進(jìn)行修改后,會(huì)強(qiáng)制將結(jié)果寫入主存嘱函,并使其它線程緩存行失效(失效后甘畅,讀操作不能從工作內(nèi)存中直接讀取,從步驟1開始),即保證3和4指令執(zhí)行過程的整體原子性疏唾,并通知其它線程蓄氧。
- 2.禁止指令重排(代碼的編寫順序和指令執(zhí)行的順序不一致),一定程度上保證了有序性槐脏。
代碼解釋重排序
if (singleInstance == null) { //4 第二次檢查
singleInstance = new SingleInstance();//5 創(chuàng)建實(shí)例
}
我們以上面代碼來簡單解釋下重排序
首先第5步代碼創(chuàng)建了一個(gè)對象喉童,這一行代碼可以分解成3個(gè)操作:
memory = allocate(); // 1:分配對象的內(nèi)存空間
ctorInstance(memory); // 2:初始化對象
singleInstance = memory; // 3:設(shè)置instance指向剛分配的內(nèi)存地址
但是,編譯器和處理器可能對指令進(jìn)行重排序顿天,比如堂氯,順序變成以下
memory = allocate(); // 1:分配對象的內(nèi)存空間
instance = memory; // 3:設(shè)置instance指向剛分配的內(nèi)存地址
// 注意,此時(shí)對象還沒有被初始化牌废!
ctorInstance(memory); // 2:初始化對象
這在單線程環(huán)境下是沒有問題的咽白,
但在多線程情況下,在B線程執(zhí)行第5步時(shí)畔规,A線程執(zhí)行到第4步局扶,由于重排序的原因,B線程還沒有完成instance引用的對象的初始化(B線程執(zhí)行了1,3)叁扫,但是A線程已經(jīng)讀取到singleInstance不為null三妈,這時(shí)候就會(huì)導(dǎo)致空指針異常。
當(dāng)我們對變量singleInstance 使用volatile修飾以后莫绣,就不會(huì)出現(xiàn)這種情況了畴蒲,volatile關(guān)鍵字會(huì)禁止指令重排,從而避免了上述情況的發(fā)生对室,
volatile修飾詞作用總結(jié):
- 1.某一線程對volatile修飾的變量進(jìn)行修改后模燥,會(huì)強(qiáng)制將結(jié)果寫入主存,并使其它線程緩存行失效(失效后掩宜,讀操作不能從工作內(nèi)存中直接讀取蔫骂,從步驟1開始),即保證3和4指令執(zhí)行過程的整體原子性牺汤,并通知其它線程——這里的步驟1234指的是上面的Java指令執(zhí)行的過程
- 2.禁止指令重排(代碼的編寫順序和指令執(zhí)行的順序不一致)辽旋,一定程度上保證了有序性。
6檐迟、使用volatile關(guān)鍵字的場景
synchronized關(guān)鍵字是防止多個(gè)線程同時(shí)執(zhí)行一段代碼补胚,那么就會(huì)很影響程序執(zhí)行效率,而volatile關(guān)鍵字在某些情況下性能要優(yōu)于synchronized追迟,但是要注意volatile關(guān)鍵字是無法替代synchronized關(guān)鍵字的溶其,因?yàn)関olatile關(guān)鍵字無法保證操作的原子性。通常來說敦间,使用volatile必須具備以下2個(gè)條件:
1)對變量的寫操作不依賴于當(dāng)前值(像++瓶逃,--就不可以)
2)該變量沒有包含在具有其他變量的不變式中
簡單的說束铭,就是上面的2個(gè)條件需要保證操作是原子性操作(參考上面講原子性時(shí)的四個(gè)例子,只能是把值賦值給變量的操作)厢绝,才能保證使用volatile關(guān)鍵字的程序在并發(fā)時(shí)能夠正確執(zhí)行纯露。
下面列舉幾個(gè)Java中使用volatile的幾個(gè)場景。
1代芜、狀態(tài)標(biāo)記量
volatile boolean inited =false;
//線程1:
context = loadContext();
inited =true; //這個(gè)賦值是原子性操作
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
2、double check(見單例模式)
7浓利、volatile和synchronized區(qū)別
- Java中的volatile關(guān)鍵字提供了一個(gè)功能挤庇,那就是被其修飾的變量在被修改后可以立即同步到主內(nèi)存,被其修飾的變量在每次是用之前都從主內(nèi)存刷新贷掖;而synchronized則是鎖定當(dāng)前變量嫡秕,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住
- volatile僅能使用在變量級別苹威,synchronized則可以使用在塊昆咽、方法;
- volatile僅能實(shí)現(xiàn)變量的修改可見性和有序性牙甫,而synchronized則可以保證變量的修改可見性掷酗、原子性、有序性窟哺。
《Java編程思想》上說泻轰,定義long或double變量時(shí),如果使用volatile關(guān)鍵字且轨,就會(huì)獲得(簡單的賦值與返回操作)原子性浮声。
- volatile僅能實(shí)現(xiàn)變量的修改可見性和有序性牙甫,而synchronized則可以保證變量的修改可見性掷酗、原子性、有序性窟哺。
- volatile不會(huì)造成線程的阻塞,而synchronized可能會(huì)造成線程的阻塞旋奢;
- 當(dāng)一個(gè)域的值依賴于它之前的值時(shí)泳挥,volatile就無法工作了,如n=n+1,n++等至朗。如果某個(gè)域的值受到其他域的值的限制屉符,那么volatile也無法工作,如Range類的lower和upper邊界爽丹,必須遵循lower<=upper的限制筑煮。
- 使用volatile而不是synchronized的唯一安全的情況是類中只有一個(gè)可變的域。
8粤蝎、synchronized和lock區(qū)別
- Lock是一個(gè)接口真仲,而synchronized是Java中的關(guān)鍵字,synchronized是內(nèi)置的語言實(shí)現(xiàn)初澎;
- synchronized在發(fā)生異常時(shí)秸应,會(huì)自動(dòng)釋放線程占有的鎖虑凛,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí)软啼,如果沒有主動(dòng)通過unLock()去釋放鎖桑谍,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖祸挪;
- Lock可以讓等待鎖的線程響應(yīng)中斷锣披,而synchronized卻不行,使用synchronized時(shí)贿条,等待的線程會(huì)一直等待下去雹仿,不能夠響應(yīng)中斷;
- 通過Lock可以知道有沒有成功獲取鎖整以,而synchronized卻無法辦到胧辽。
- Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。
- 在性能上來說公黑,如果競爭資源不激烈邑商,兩者的性能是差不多的,而當(dāng)競爭資源非常激烈時(shí)(即有大量線程同時(shí)競爭)凡蚜,此時(shí)Lock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized人断。所以說,在具體使用時(shí)要根據(jù)適當(dāng)情況選擇番刊。
synchronized和lock的詳細(xì)介紹以及底層實(shí)現(xiàn)見Java 鎖
9含鳞、Java內(nèi)存屏障
9.1 為什么會(huì)有內(nèi)存屏障
每個(gè)CPU都會(huì)有自己的緩存(有的甚至L1,L2,L3),緩存的目的就是為了提高性能芹务,避免每次都要向內(nèi)存取蝉绷。但是這樣的弊端也很明顯:不能實(shí)時(shí)的和內(nèi)存發(fā)生信息交換,分在不同CPU執(zhí)行的不同線程對同一個(gè)變量的緩存值不同枣抱。
那怎么解決上述問題呢熔吗?那就是用內(nèi)存屏障,內(nèi)存屏障是硬件層的概念佳晶,不同的硬件平臺(tái)實(shí)現(xiàn)內(nèi)存屏障的手段并不是一樣桅狠,java通過屏蔽這些差異,統(tǒng)一由jvm來生成內(nèi)存屏障的指令轿秧。
內(nèi)存屏障有兩個(gè)作用:
- 阻止屏障兩側(cè)的指令重排序中跌;
- 強(qiáng)制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存,讓緩存中相應(yīng)的數(shù)據(jù)失效菇篡。
9.2 內(nèi)存屏障是什么
硬件層的內(nèi)存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障漩符。
- Load Barrier:在指令前插入Load Barrier,可以讓高速緩存中的數(shù)據(jù)失效驱还,強(qiáng)制重新從主內(nèi)存加載數(shù)據(jù)嗜暴,即刷新處理器緩存凸克;
- Store Barrier:在指令后插入Store Barrier,可以讓寫入緩存中的最新數(shù)據(jù)更新寫入主內(nèi)存闷沥,讓其他線程可見萎战,即刷新處理器主存。
9.3 java內(nèi)存屏障
java的內(nèi)存屏障通常所謂的四種即LoadLoad舆逃,StoreStore蚂维,LoadStore,StoreLoad實(shí)際上也是上述兩種的組合路狮,完成一系列的屏障和數(shù)據(jù)同步功能鸟雏。
- LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前览祖,保證Load1要讀取的數(shù)據(jù)被讀取完畢,即保證了在屏障前的讀取操作效果先于屏障后的讀取操作效果發(fā)生炊琉;
- LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2展蒂,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢苔咪,即保證了屏障前的讀操作效果先于屏障后的寫操作效果發(fā)生
- StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2锰悼,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見团赏,保證了在屏障前的寫操作效果先于屏障后的寫操作效果發(fā)生箕般;
- StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前舔清,保證Store1的寫入對所有處理器可見(強(qiáng)制把寫緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫回主內(nèi)存)丝里,保證了屏障前的寫操作效果先于屏障后的讀操作效果發(fā)生
它的開銷是四種屏障中最大的。在大多數(shù)處理器的實(shí)現(xiàn)中体谒,這個(gè)屏障是個(gè)萬能屏障杯聚,兼具其它三種內(nèi)存屏障的功能
9.4 volatile語義中的內(nèi)存屏障
volatile的內(nèi)存屏障策略非常嚴(yán)格保守,非常悲觀且毫無安全感的心態(tài):
在每個(gè)volatile讀操作前插入LoadLoad屏障抒痒,在讀操作后插入LoadStore屏障幌绍;——讀屏障Load Barrier
在每個(gè)volatile寫操作前插入StoreStore屏障,在寫操作后插入StoreLoad屏障故响;——寫屏障Store Barrier
由于內(nèi)存屏障的作用傀广,避免了volatile變量和其它指令重排序、線程之間實(shí)現(xiàn)了通信彩届,使得volatile表現(xiàn)出了鎖的特性伪冰。
9.4 synchronized語義中的內(nèi)存屏障
Java 虛擬機(jī)會(huì)在 MonitorEnter ( 申請鎖 ) 對應(yīng)的機(jī)器碼指令之后臨界區(qū)開始之前的地方插入一個(gè)加載屏障(Load Barrier),這可以讓高速緩存中的數(shù)據(jù)失效惨缆,強(qiáng)制重新從主內(nèi)存加載數(shù)據(jù)糜值,即刷新處理器緩存丰捷;
Java虛擬機(jī)會(huì)在 MonitorExit ( 釋放鎖 ) 對應(yīng)的機(jī)器碼指令之后插入一個(gè)存儲(chǔ)屏障(Store Barrier),這可以讓寫入緩存中的最新數(shù)據(jù)更新寫入主內(nèi)存寂汇,讓其他線程可見病往,即刷新處理器主存;