小編帶您Volatile的詳解

volatile關(guān)鍵字修飾的共享變量主要有兩個特點:1.保證了不同線程訪問的內(nèi)存可見性 2.禁止重排序

在說內(nèi)存可見性和有序性之前顷级,我們有必要看一下Java的內(nèi)存模型(注意和JVM內(nèi)存模型的區(qū)分)

為什么要有java內(nèi)存模型?

首先我們知道內(nèi)存訪問和CPU指令在執(zhí)行速度上相差非常大确沸,完全不是一個數(shù)量級,為了使得java在各個平臺上運行的差距減少怎虫,哪些搞處理器的大佬就在CPU上加了各種高速緩存喊熟,來減少內(nèi)存操作和CPU指令的執(zhí)行速度差距。而Java在java層面又進行了一波抽象达皿,java內(nèi)存模型將內(nèi)存分為工作內(nèi)存和主存天吓,每個線程從主存load數(shù)據(jù)到工作內(nèi)存贿肩,將load的數(shù)據(jù)賦值給工作內(nèi)存上的變量,然后該工作內(nèi)存對應(yīng)的線程進行處理龄寞,處理結(jié)果在賦值給其工作內(nèi)存汰规,然后再將數(shù)據(jù)賦值給主存中的變量(這時候需要有一張圖)。

小編帶您Volatile的詳解

使用工作內(nèi)存和主存雖然加快了處理速度物邑,但是也帶來了一些問題溜哮,比如下面這個例子

1 int i = 1;

2 i = i+1;

當(dāng)在單線程情況下,i最后的值一定是2色解;但是在兩個線程情況下一定是3嗎茂嗓?那就未必了。當(dāng)線程A讀取i的值為1科阎,load到其工作內(nèi)存述吸,這時CPU切換至線程B,線程B讀取i的值也是1萧恕,然后對加1然后save到主存刚梭,這時線程A也對i進行加1,也save回主存票唆,但最終i的值為2朴读。如果寫操作比較慢,你讀到的值還有可能是1走趋,這就是緩存不一致的問題衅金。JMM就是圍繞著原子性,內(nèi)存可見性簿煌,有序性這三個特征建立的氮唯。通過解決這個三個特征來解決緩存不一致的問題。而volatile主要針對于內(nèi)存可見性和有序性姨伟。

原子性

原子性是指一個操作要么成功惩琉,那么失敗,沒有中間狀態(tài)夺荒,比如i=1瞒渠,直接讀取i的值,這肯定是原子操作技扼;但是i++伍玖,看似好像是,其實需要先讀取i的值剿吻,然后+1窍箍,最后在賦值給i,需要三個步驟,這就不是原子性操作椰棘。在JDK1.5引入了boolean纺棺、long、int對應(yīng)的原子性類AtomicBoolean晰搀、AtomicLong五辽、AtomicInteger,他們可以提供原子性操作外恕。

內(nèi)存可見性

具有內(nèi)存可見性的變量在被線程修改以后,會立刻刷新到主存并使其他線程的緩存行上的數(shù)據(jù)失效乡翅。

volatile修飾的變量具有內(nèi)存可見性鳞疲,主要表現(xiàn)為:當(dāng)寫一個volatile變量時,JMM會將該線程對應(yīng)的工作內(nèi)存中的共享變量立即刷新到主存蠕蚜;當(dāng)讀一個volatile變量時尚洽,JMM會把該線程對應(yīng)的工作內(nèi)存中的值置為無效,然后從主存中進行讀取靶累,但是如果沒有線程對該共享變量進行修改腺毫,則不會觸發(fā)該操作。

有序性

JMM是允許處理器和編譯器對指令進行重排序的挣柬,但規(guī)定了as-if-serial潮酒,即無論怎么重排序,最終結(jié)果都是一樣的邪蛔。比如下面這段代碼:

1 int weight = 10; //A

2 int high = 5; //B

3 int area = high * weight * high; //C

這段代碼中可以按照A-->B-->C執(zhí)行急黎,也可以按照B-->A-->C執(zhí)行,因為A和B是相互獨立的侧到,而C依賴于A勃教、B,所以C不能排到A或B的前面匠抗。JMM保證了單線程的重排序故源,但是在多線程中就容易出現(xiàn)問題。比如下面這種情況

小編帶您Volatile的詳解

1 boolean flag = false;

2 int a = 0;

4 public void write(){

5 int a = 2; //1

6 flag = true; //2

7 }

8 public void multiply(){

9 if(flag){ //3

10 int ret = a * a ; //4

11 }

12 }

小編帶您Volatile的詳解

如果有兩個線程執(zhí)行上面的代碼汞贸,線程1先執(zhí)行write方法绳军,隨后線程2執(zhí)行multiply方法。最后結(jié)果一定是4嗎著蛙,不一定删铃。

小編帶您Volatile的詳解

如圖,JMM對1和2進行了重排序踏堡,先將flag設(shè)置為true猎唁,這是線程2執(zhí)行,由于a還沒有賦值,所以最后ret的值為0诫隅;

如果使用volatile關(guān)鍵字修飾flag腐魂,禁止重排序,可以保證程序的有序性逐纬,也可以使用synchronized或者lock這種重量級鎖來保證有序性蛔屹,但性能會下降。

另外豁生,JMM具備一些先天的有序性,即不需要通過任何手段就可以保證的有序性兔毒,通常稱為happens-before原則。<>定義了如下happens-before規(guī)則:

第1條規(guī)則程序順序規(guī)則是說在一個線程里甸箱,所有的操作都是按順序的育叁,但是在JMM里其實只要執(zhí)行結(jié)果一樣,是允許重排序的芍殖,這邊的happens-before強調(diào)的重點也是單線程執(zhí)行結(jié)果的正確性豪嗽,但是無法保證多線程也是如此。

第2條規(guī)則監(jiān)視器規(guī)則其實也好理解豌骏,就是在加鎖之前龟梦,確定這個鎖之前已經(jīng)被釋放了,才能繼續(xù)加鎖窃躲。

第3條規(guī)則计贰,就適用到所討論的volatile,如果一個線程先去寫一個變量框舔,另外一個線程再去讀蹦玫,那么寫入操作一定在讀操作之前。

第4條規(guī)則刘绣,就是happens-before的傳遞性樱溉。

需要注意的是,被volatile修飾的共享變量只滿足內(nèi)存可見性和禁止重排序纬凤,并不能保證原子性福贞。比如volatile i++。

小編帶您Volatile的詳解

1 public class Test {

2 public volatile int inc = 0;

4 public void increase() {

5 inc++;

6 }

8 public static void main(String[] args) {

9 final Test test = new Test();

10 for(int i=0;i<10;i++){

11 new Thread(){

12 public void run() {

13 for(int j=0;j<1000;j++)

14 test.increase();

15 };

16 }.start();

17 }

18

19 while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完

20 Thread.yield();

21 System.out.println(test.inc);

22 }

小編帶您Volatile的詳解

按道理來說結(jié)果是10000停士,但是運行下很可能是個小于10000的值挖帘。有人可能會說volatile不是保證了可見性啊,一個線程對inc的修改恋技,另外一個線程應(yīng)該立刻看到澳匆ā!可是這里的操作inc++是個復(fù)合操作啊蜻底,包括讀取inc的值骄崩,對其自增,然后再寫回主存。

假設(shè)線程A要拂,讀取了inc的值為10抠璃,這時候被阻塞了,因為沒有對變量進行修改脱惰,觸發(fā)不了volatile規(guī)則搏嗡。

線程B此時也讀讀inc的值,主存里inc的值依舊為10拉一,做自增采盒,然后立刻就被寫回主存了,為11舅踪。

此時又輪到線程A執(zhí)行纽甘,由于工作內(nèi)存里保存的是10,所以繼續(xù)做自增抽碌,再寫回主存,11又被寫了一遍决瞳。所以雖然兩個線程執(zhí)行了兩次increase()货徙,結(jié)果卻只加了一次。

有人說皮胡,volatile不是會使緩存行無效的嗎痴颊?但是這里線程A讀取到線程B也進行操作之前,并沒有修改inc值屡贺,所以線程B讀取的時候蠢棱,還是讀的10。

又有人說甩栈,線程B將11寫回主存泻仙,不會把線程A的緩存行設(shè)為無效嗎?但是線程A的讀取操作已經(jīng)做過了啊量没,只有在做讀取操作時玉转,發(fā)現(xiàn)自己緩存行無效,才會去讀主存的值殴蹄,所以這里線程A只能繼續(xù)做自增了究抓。

綜上所述,在這種復(fù)合操作的情景下袭灯,原子性的功能是維持不了了刺下。但是volatile在上面那種設(shè)置flag值的例子里,由于對flag的讀/寫操作都是單步的稽荧,所以還是能保證原子性的橘茉。

要想保證原子性,只能借助于synchronized,Lock以及并發(fā)包下的atomic的原子操作類了,即對基本數(shù)據(jù)類型的 自增(加1操作)捺癞,自減(減1操作)夷蚊、以及加法操作(加一個數(shù)),減法操作(減一個數(shù))進行了封裝髓介,保證這些操作是原子性操作惕鼓。

volatile底層原理

如果將使用volatile修飾的代碼和未使用volatile修飾的代碼都編譯成匯編語言,會發(fā)現(xiàn)唐础,使用volatile修飾的代碼會多出一個lock前綴指令箱歧。

lock前綴指令相當(dāng)于一個內(nèi)存屏障,內(nèi)存屏障的作用有以下三點:

①重排序時一膨,不能把內(nèi)存屏障后面的指令排序到內(nèi)存屏障前

②使得本CPU的cache寫入內(nèi)存

③寫入動作會引起其他CPU緩存或內(nèi)核的數(shù)據(jù)無效呀邢,相當(dāng)于修改對其他線程可見。

volatile的應(yīng)用場景

因為volatile對復(fù)合操作無效豹绪,所以volatile修飾像上面例子中的flag這樣的只會發(fā)生讀/寫的標(biāo)記型字段价淌。

在單利模式中,volatile還可以修飾成員變量瞒津,防止初始化時的指令重排序蝉衣。

小編帶您Volatile的詳解

1 class Singleton{

2 private volatile static Singleton instance= null;

4 private Singleton(){

6 }

8 public static Singleton getInstance(){

9 if(instance==null){

10 synchronized(Singleton.class){

11 if(instance==null){

12 instance = new Singleton();

13 }

14 }

15 }

16 return instance;

17 }

18 } 小編每天會定期更新論文及視頻,希望大家多多關(guān)注與支持 每天晚上20:00會在騰訊課堂上分享免費往期干貨QQ:561487941

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巷蚪,一起剝皮案震驚了整個濱河市病毡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屁柏,老刑警劉巖啦膜,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淌喻,居然都是意外死亡僧家,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門似嗤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啸臀,“玉大人,你說我怎么就攤上這事烁落〕肆#” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵伤塌,是天一觀的道長灯萍。 經(jīng)常有香客問我,道長每聪,這世上最難降的妖魔是什么旦棉? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任齿风,我火速辦了婚禮,結(jié)果婚禮上绑洛,老公的妹妹穿的比我還像新娘救斑。我一直安慰自己,他們只是感情好真屯,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布脸候。 她就那樣靜靜地躺著,像睡著了一般绑蔫。 火紅的嫁衣襯著肌膚如雪运沦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天配深,我揣著相機與錄音携添,去河邊找鬼。 笑死篓叶,一個胖子當(dāng)著我的面吹牛烈掠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缸托,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼向叉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗦董?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘦黑,失蹤者是張志新(化名)和其女友劉穎京革,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幸斥,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡匹摇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了甲葬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廊勃。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖经窖,靈堂內(nèi)的尸體忽然破棺而出坡垫,到底是詐尸還是另有隱情,我是刑警寧澤画侣,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布冰悠,位于F島的核電站,受9級特大地震影響配乱,放射性物質(zhì)發(fā)生泄漏溉卓。R本人自食惡果不足惜皮迟,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桑寨。 院中可真熱鬧伏尼,春花似錦、人聲如沸尉尾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽代赁。三九已至扰她,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芭碍,已是汗流浹背徒役。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窖壕,地道東北人忧勿。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像瞻讽,于是被迫代替她去往敵國和親鸳吸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容