volatile 關(guān)鍵字分析

點擊上方「藍(lán)字」關(guān)注我們

一、volatile變量語義一的概念
當(dāng)一個變量被定義成volatile之后,具備兩個特性:
特性一:保證此變量對所有線程的可見性。這里的“可見性”是指當(dāng)一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的。而普通變量并不能做到這一點,普通變量的值在線程傳遞時均需要通過主內(nèi)存來完成。比如:線程A修改了一個普通變量的值,然后向主內(nèi)存進(jìn)行回寫,另一條線程B在線程A回寫完成了之后再對主內(nèi)存進(jìn)行讀取操作,新變量值才會對線程B可見。
二、volatile能夠保證線程安全嗎
基于volatile變量在各個線程中是不存在一致性問題的,從物理存儲的角度看,各個線程的工作內(nèi)存中volatile變量也可以存在不一致的情況,但是由于每次使用前都要進(jìn)行刷新,執(zhí)行引擎看不到不一致的情況,因此也可以人為不存在一致性問題,但是java里面的運算操作符并非是原子操作,這導(dǎo)致了volatile變量的運算在并發(fā)下一樣是不安全的。
案例代碼:
/**
?*?測試Volatile的特性
?*/
public?class?VolatileTest?{
public?static?volatile??int?race?=?0;
public?static?void?increase(){
???? race++;
}
//定義線程的數(shù)量
private?static?final?int?THREADS_COUNT?=?20;
public?static?void?main(String[]?args)?{
???? Thread[]?threads?=?new?Thread[THREADS_COUNT];
???? for(int?i?=?0;i???????? threads[i]?=?new?Thread(new?Runnable()?{
???????????? @Override
??????????? ?public?void?run()?{
??????????????? for(int?j?=?0;j<1000;j++){
??????????????????? increase();
??????????????? }
???????????? }
???????? });
???????? threads[i].start();
???????? System.out.println("線程"+i+"開始執(zhí)行");
???? }
???? while?(Thread.activeCount()>2){
???????? System.out.println("Thread.activeCount()?=?"+Thread.activeCount());
???????? Thread.yield();//有其他線程等待時,將該線程設(shè)置為就緒狀態(tài)。
??? ?}
???? System.out.println("race:"+race);
}
} 這段代碼發(fā)起了20個線程,每個線程都對race變量的做了10000次的自增操作,如果是正常的并發(fā)的話,那么race的結(jié)果用該是200000,可是執(zhí)行幾次,發(fā)現(xiàn)結(jié)果并不是200000,而都是一個小于200000的值。這是為什么呢?因為++操作本身就不是原子的,要經(jīng)過讀取計算和寫回,那么,我們通過一張圖模仿一下以上代碼:

由于變量被volatile修飾,因此這張圖中的3,4操作是連續(xù)不間斷的,5,6,7的操作也是連續(xù)不間斷的,但是經(jīng)過兩個線程的讀取修改寫回操作后,i的值僅僅從1變?yōu)榱?,并不是我們想象的3,
可能在這里理解上述的圖和描述有點抽象,因為有的朋友可能并不能理解數(shù)據(jù)在主存和緩存中的讀取更改的傳遞規(guī)則,在這里,補充一下變量在內(nèi)存之間的相互操作知識點,大家可以先看以下這塊內(nèi)容,再回過頭進(jìn)行理解上述圖中的操作。
三:內(nèi)存間的相互操作
·lock(鎖定):作用于主內(nèi)存的變量,它把一個變量標(biāo)識為一條線程獨占的狀態(tài)。
·unlock(解鎖):作用于主內(nèi)存的變量,它把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
·read(讀取):作用于主內(nèi)存的變量,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用。
·load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
·use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
·assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
·store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。
·write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

? 由volatile修飾的變量的特性保證此線程的可見性可知,當(dāng)我們使用volatile修飾了一個變量后,一個線程對此變量的修改對于其他線程來講是立即可知的,也就是說.assign,.store,.write這三個操作是原子的,中間不會間斷,會馬上的同步主存,就像直接操作主存一樣,并通過緩存一致性通知其他的緩存中的副本過期。普通變量可能會在.assign,.store,.write這三個操作中插入其他的操作,導(dǎo)致更改后的數(shù)據(jù)不能立即同步回主存,這種情況在volatile修飾變量時是不存在的。
四:使用volatile控制并發(fā)的場景
由于volatile變量只能保證可見性,在不符合以下兩條規(guī)則的運算場景中,我們?nèi)匀灰ㄟ^加鎖(使用synchronized、java.util.concurrent中的鎖或原子類)來保證原子性:
1、運算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值。
2、變量不需要與其他的狀態(tài)變量共同參與不變約束。
舉個例子:
class?VolatileOne{
volatile?boolean?isShutDown;
public?void?shutDown(){
???? isShutDown?=?true;
}
public?void?dowork(){
???? while?(!isShutDown){
???????? //業(yè)務(wù)代碼
???? }
}
}這類場景就比較適合使用volatile控制并發(fā),當(dāng) shutDown()方法被調(diào)用時,能保證所有線程中執(zhí)行的dowork()方法都立即停下來。使用volatile變量的第二個特性是禁止指令重排優(yōu)化。

掃碼二維碼
獲取更多精彩
Java樂園

