學會了volatile,你變心了,我看到了

更多精彩文章,請關注xhJaver,京東工程師和你一起成長

volatile 簡介

一般用來修飾共享變量,保證可見性和可以禁止指令重排

  • 多線程操作同一個變量的時候,某一個線程修改完,其他線程可以立即看到修改的值,保證了共享變量的可見性

  • 禁止指令重排,保證了代碼執行的有序性

  • 不保證原子性,例如常見的i++

    (但是對單次讀或者寫保證原子性)

可見性代碼示例

以下代碼建議使用PC端來查看,復制黏貼直接運行,都有詳細注釋

我們來寫個代碼測試一下,多線程修改共享變量時究竟需不需要用volatile修飾變量

  1. 首先,我們創建一個任務類
public class Task implements Runnable{
   @Override
   public void run() {
       System.out.println("這是"+Thread.currentThread().getName()+"線程開始,flag是 "+Demo.flag);
       //當共享變量是true時,就一直卡在這里,不輸出下面那句話
       // 當flag是false時,輸出下面這句話
       while (Demo.flag){

       }
       System.out.println("這是"+Thread.currentThread().getName()+"線程結束,flag是 "+Demo.flag);
   }
}

2.其次,我們創建個測試類

class Demo {

   //共享變量,還沒用volatile修飾
   public static   boolean flag = true ;
   public static void main(String[] args) throws InterruptedException {
       System.out.println("這是"+Thread.currentThread().getName()+"線程開始,flag是 "+flag);
       //開啟剛才線程
       new Thread(new Task()).start();
       try {
           //沉睡一秒,確保剛才的線程已經跑到了while循環
           //要不然還沒跑到while循環,主線程就將flag變為false
           Thread.sleep(1000L);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       //改變共享變量flag轉為false
       flag = false;
       System.out.println("這是"+Thread.currentThread().getName()+"線程結束,flag是 "+flag);
   }
}

3.我們查看一下輸出結果

無volatile修飾.jpg

可見,程序并沒有結束,他卡在了這里,為什么卡在了這里呢,就是因為我們在主線程修改了共享變量flag為false,但是另一個線程沒有感知到,這個變量的修改對另一個線程不可見

  • 如果要是用volatile變量修飾的話,結果就變成了下面這個樣子

public static volatile boolean flag = true

有volatile修飾.jpg

可見,這次主線程修改的變量被另一個線程所感知到了,保證了變量的可見性

可見性原理分析

那么,神奇的 volatile 底層到底做了什么呢,你的改變,逃不過他的法眼?為什么不用他修飾變量的話,變量的改變其他線程就看不見?

回答此問題的時候首先,我們需要了解一下JMM(Java內存模型)

JMM.jpg

注:本地內存是JMM的一種抽象,并不是真實存在的,本地內存它涵蓋了緩存,寫緩沖區,寄存器以及其他的硬件和編譯器優化之后的一個數據存放位置

  • 由此我們可以分析出來,主線程修改了變量,但是其他線程不知道,有兩種情況

    1. 主線程修改的變量還沒有來得及刷新到主內存中,另一個線程讀取的還是以前的變量
    2. 主線程修改的變量刷新到了主內存中,但是其他線程讀取的還是本地的副本
  • 當我們用 volatile 關鍵字修飾共享變量時就可以做到以下兩點

    1. 當線程修改變量時,會強制刷新到主內存中
    2. 當線程讀取變量時,會強制從主內存讀取變量并且刷新到工作內存中

指令重排

  • 何為指令重排?

為了提高程序運行效率,編譯器和cpu會對代碼執行的順序進行重排列,可這有時候會帶來很多問題

我們來看下代碼

//指令重排測試
public class Demo2 {

   private Integer number = 10;
   private boolean flag = false;
   private Integer result = 0;

   public void  write(){
       this.flag = true; // L1
       this.number = 20; // L2
   }

   public void  reader(){
        while (this.flag){ // L3
            this.result = this.number + 1; // L4
        }
   }
}

假如說我們有A、B兩個線程 他們分別執行write()方法和 reader()方法,執行的順序有可能如下圖所示


重排序.jpg
  • 問題分析: 如圖可見,A線程的L2和L1的執行順序重排序了,如果要是這樣執行的話,當A執行完L2時,B開始執行L3,可是這個時候flag還是為false,那么L4就執行不了了,所以result的值還是初始值0,沒有被改變為21,導致程序執行錯誤

這個時候,我們就可以用volatile關鍵字來解決這個問題,很簡單,只需

private volatile Integer number = 10;

  • 這個時候L1就一定在L2前面執行

A線程在修改number變量為20的時候,就確保這句代碼的前面的代碼一定在此行代碼之前執行,在number處插入了內存屏障 ,為了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排

內存屏障

內存屏障又是什么呢?一共有四種內存屏障類型,他們分別是

  1. LoadLoad屏障:

    • Load1 LoadLoad Load2 確保Load1的數據的裝載先于Load2及所有后續裝載指令的裝載
  2. LoadStore屏障:

    • Load1 LoadStore Store2 確保Load1的數據的裝載先于Store2及所有后續存儲指令的存儲
  3. StoreLoad屏障:

    • Store1 StoreLoad Load2 確保Store1的數據對其他處理器可見(刷新到內存)先于Load2及所有后續的裝載指令的裝載
  4. StoreStore屏障:

    • Store1 StoreStore Store2 確保Store1數據對其他處理器可見(刷新到內存)先于Store2及所有后續存儲指令的存儲

    StoreLoad 是一個全能型的屏障,同時具有其他3個屏障的效果。執行該屏障的花銷比較昂貴,因為處理器通常要把當前的寫緩沖區的內容全部刷新到內存中(Buffer Fully Flush)

  • 裝載load 就是讀 int a = load1 ( load1的裝載)
  • 存儲store就是寫 store1 = 5 ( store1的存儲)

volatile與內存屏障

那么volatile和這四種內存屏障又有什么關系呢,具體是怎么插入的呢?

  1. volatile寫 (前后都插入屏障)

    • 前面插入一個StoreStore屏障
    • 后面插入一個StoreLoad屏障


      volatile寫屏障.jpg
  2. volatile讀(只在后面插入屏障)

    • 后面插入一個LoadLoad屏障
    • 后面插入一個LoadStore屏障


      volatile讀內存屏障.jpg

官方提供的表格是這樣的


內存屏障表格.jpg

我們此時回過頭來在看我們的那個程序

<pre class="custom" data-tool="mdnice編輯器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">this.flag = true; // L1 this.number = 20; // L2 </pre>

由于number被volatile修飾了,L2這句話是volatile寫,那么加入屏障后就應該是這個樣子

this.flag = true; // L1
 //  StoreStore  確保flag數據對其他處理器可見(刷新到內存)先于number及所有后續存儲指令的存儲
 this.number = 20; // L2
 // StoreLoad  確保number數據對其他處理器可見(刷新到內存)先于所有后續存儲指令的裝載

所以L1,L2的執行順序不被重排序

更多精彩,請關注xhJaver,京東工程師和你一起成長

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • 在實際的工作中,可能我們比較少去使用的一個關鍵字就是volatile,但是觀察源碼的時候卻是經常遇到。如果不搞懂何...
    瑜小賢閱讀 687評論 0 1
  • 一旦一個共享變量(類的成員變量、 類的靜態成員變量) 被 volatile 修飾之后, 那么就具備了兩層語義: 保...
    六尺帳篷閱讀 1,008評論 0 9
  • 轉自:https://juejin.im/editor/drafts/5acda6976fb9a028d93782...
    簡步超閱讀 147評論 0 0
  • 加州大學圣地亞哥分校(美國)校訓:“愿知識之光普照大地。” 夏季,收獲的季節,可看著A股大盤一直趴著,又是徒勞了半...
    阿倫故事2019閱讀 390評論 0 1
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經沒多少時間了。班主任說已經安排了三個家長分享經驗。 放學鈴聲...
    飄雪兒5閱讀 7,549評論 16 22