概述
? 在多線程訪問(wèn)共享變量時(shí)傻工,java通過(guò)volatile關(guān)鍵字保證變量的可見性,相比于synchronized關(guān)鍵字咙冗,volatile并不會(huì)引起線程上下文的切換沾歪,因此具有較小的開銷。同時(shí)乞娄,volatile可以禁止指令重排序瞬逊。這篇文章將對(duì)以上兩點(diǎn)作用的,希望可以加深對(duì)一些概念的理解仪或。
volatile保證變量可見性
? 共享變量的可見性,簡(jiǎn)單的說(shuō)就是在一個(gè)線程中修改了變量士骤,在另一個(gè)線程可以讀到修改后的變量范删。在對(duì)這塊實(shí)現(xiàn)原理進(jìn)行介紹之前,我們必須對(duì)java內(nèi)存模型有一定的了解拷肌。
java內(nèi)存模型
? java內(nèi)存模型規(guī)定了所有的共享變量(除局部變量及方法參數(shù))都存儲(chǔ)在主內(nèi)存中到旦,而每個(gè)線程也有自己的工作內(nèi)存,線程的工作內(nèi)存保存了該線程使用到的變量的主內(nèi)存的副本巨缘,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行添忘,而不能直接讀取主內(nèi)存中的變量,具體如下如所示:
主內(nèi)存和工作內(nèi)存間的交互通過(guò)如下8中操作來(lái)完成若锁,可以看出主內(nèi)存變量值到工作內(nèi)存可以通過(guò)read和load完成搁骑,工作內(nèi)存到主內(nèi)存變量值可以通過(guò)store和write完成。
(1) lock(鎖定):作用于主內(nèi)存,把一個(gè)變量標(biāo)識(shí)為一個(gè)線程獨(dú)占的狀態(tài)仲器;
(2) unlock(解鎖):作用于主內(nèi)存的變量煤率,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定乏冀。
(3) read(讀取):作用于主內(nèi)存蝶糯,把一個(gè)變量從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存;
(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)存的變量中。
? 對(duì)于非volatile變量讀取變量值會(huì)直接執(zhí)行l(wèi)oad谨履,use操作從工作內(nèi)存中讀取變量欢摄,這種可能會(huì)出現(xiàn)讀取的變量值是失效的,與主內(nèi)存中的不一致笋粟。volatile變量通過(guò)強(qiáng)制要求read怀挠,load,use必須連續(xù)出現(xiàn)保證每次讀取前都從主內(nèi)存中刷新最新的值害捕。通過(guò)要求assign绿淋,store,write必須連續(xù)出現(xiàn)保證了修改的數(shù)據(jù)立刻同步到主內(nèi)存中尝盼,這種就保證了變量的可見性吞滞。
volatile禁止指令重排序
? 指令重排序是指編譯器和處理器為了優(yōu)化程序執(zhí)行順序?qū)Τ绦虻膱?zhí)行語(yǔ)句進(jìn)行優(yōu)化。舉一個(gè)簡(jiǎn)單的創(chuàng)建對(duì)象的語(yǔ)句:
dclProblemSingleton = new DCLProblemSingleton();
這條語(yǔ)句會(huì)被jvm解析為以下三個(gè)操作執(zhí)行:(1) 為對(duì)象分配內(nèi)存盾沫;(2) 為對(duì)象賦值裁赠;(3)將內(nèi)存地址賦值給dclProblemSingleton變量。由于(1)和(3)并不存在依賴關(guān)系赴精,所以指令重排序后語(yǔ)句執(zhí)行的順序可能是(1)(3)(2)佩捞。這個(gè)時(shí)候如果作為共享變量多線程訪問(wèn)就會(huì)存在問(wèn)題。經(jīng)典的DCL雙重檢查鎖實(shí)現(xiàn)的飽漢模式的單例就會(huì)存在這個(gè)問(wèn)題蕾哟,可能存在又有的線程訪問(wèn)到未被初始化的對(duì)象一忱,具體代碼如下:
public class DCLProblemSingleton {
private int ele = 0;
private static DCLProblemSingleton dclProblemSingleton;
private DCLProblemSingleton() {}
public static DCLProblemSingleton getDclProblemSingleton() {
//第一次檢測(cè)
if (dclProblemSingleton == null) {
synchronized (DCLProblemSingleton.class) {
//第二次檢測(cè)
if (dclProblemSingleton == null) {
dclProblemSingleton = new DCLProblemSingleton();//可能出現(xiàn)問(wèn)題的地方
}
}
}
return dclProblemSingleton;
}
}
正是由于java中的指令重排序莲蜘,DCL方式實(shí)現(xiàn)的指令重排序會(huì)存在問(wèn)題,解決這個(gè)問(wèn)題的方式就是通過(guò)將dclProblemSingleton聲明為volatile來(lái)禁止指令重排序掀潮。
內(nèi)存屏障
JDK1.5中已引入了內(nèi)存屏障菇夸,volatile關(guān)鍵字是通過(guò)內(nèi)存屏障(柵欄)來(lái)實(shí)現(xiàn)的禁止質(zhì)量重排序。內(nèi)存屏障又稱為內(nèi)存柵欄仪吧,是一組cpu指令庄新。不同架構(gòu)的cpu的內(nèi)存屏障實(shí)現(xiàn)都不相同,我們以Intel x86為例子薯鼠,其實(shí)現(xiàn)了如下內(nèi)存柵欄:
(1) sfence: 確保sfence前所有的store先與sfence所有的store命令執(zhí)行择诈;
(2) Ifence: 確保Ifence前所有的load先與Ifence所有的load命令執(zhí)行;
(3) mfence: 功能相當(dāng)于sfence和mfence總和出皇。
(4) Lock前綴羞芍,Lock不是一種內(nèi)存屏障,但是它能完成類似內(nèi)存屏障的功能郊艘。Lock會(huì)對(duì)CPU總線和高速緩存加鎖荷科,可以理解為CPU指令級(jí)的一種鎖。
有了上述的內(nèi)存屏障命令我們看下volatile是如何實(shí)現(xiàn)禁止指令重排序的纱注?
在x86平臺(tái)上volatile是通過(guò)Lock前綴實(shí)現(xiàn)的畏浆,Lock前綴先對(duì)總線/緩存加鎖,然后執(zhí)行后面的指令狞贱,最后釋放鎖后會(huì)把高速緩存中的臟數(shù)據(jù)全部刷新回主內(nèi)存刻获。在Lock前綴鎖住總線/緩存期間,其他cpu的讀寫請(qǐng)求都會(huì)被阻塞直到釋放瞎嬉,Lock后的寫操作會(huì)會(huì)使得其他cpu的的緩存失效蝎毡。例如以上的賦值語(yǔ)句dclProblemSingleton = new DCLProblemSingleton(),如果dclProblemSingleton被volatile關(guān)鍵字修飾氧枣,那么執(zhí)行(3)上加上lock沐兵,那么(3)前面的步驟(1)(2)不執(zhí)行完成就無(wú)法執(zhí)行(3),也就禁止了指令重排序挑胸。在lock釋放后痒筒,又會(huì)將緩存刷新到主存中。
內(nèi)存屏障與上述的可見性實(shí)現(xiàn)是否有關(guān)系茬贵?
在上面的lock操作中,在lock釋放后會(huì)將緩存刷新到主存中移袍,也就是我們上面所將的assign解藻,store,write必須連續(xù)出現(xiàn)葡盗。我理解volatile的可見性在jvm層面是通過(guò)read螟左,load啡浊,use必須連續(xù)出現(xiàn),assign胶背,store巷嚣,write必須連續(xù)出現(xiàn)保證的。而這個(gè)連續(xù)出現(xiàn)則是內(nèi)存屏障使用的表象钳吟,底層原理還是依靠?jī)?nèi)存屏障來(lái)解決廷粒。
原文
袁瓊瓊的技術(shù)博客,歡迎指針
http://yuanqiongqiong.cn/2019/06/09/%E8%B0%88%E8%B0%88volatile%E5%85%B3%E9%94%AE%E5%AD%97/