<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          學(xué)會了volatile,你變心了,我看到了

          共 5873字,需瀏覽 12分鐘

           ·

          2021-06-26 00:54

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

          volatile 簡介

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

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

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

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

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

          可見性代碼示例

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

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

          1. 首先,我們創(chuàng)建一個任務(wù)類

          public class Task implements Runnable{
              @Override
              public void run() {
                  System.out.println("這是"+Thread.currentThread().getName()+"線程開始,flag是 "+Demo.flag);
                  //當(dāng)共享變量是true時,就一直卡在這里,不輸出下面那句話
                  // 當(dāng)flag是false時,輸出下面這句話
                  while (Demo.flag){

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

          2.其次,我們創(chuàng)建個測試類


          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 {
                      //沉睡一秒,確保剛才的線程已經(jīng)跑到了while循環(huán)
                      //要不然還沒跑到while循環(huán),主線程就將flag變?yōu)閒alse
                      Thread.sleep(1000L);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  //改變共享變量flag轉(zhuǎn)為false
                  flag = false;
                  System.out.println("這是"+Thread.currentThread().getName()+"線程結(jié)束,flag是 "+flag);
              }
          }

          3.我們查看一下輸出結(jié)果

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

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

          public static volatile boolean flag = true

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

          可見性原理分析

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

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

          注:本地內(nèi)存是JMM的一種抽象,并不是真實存在的,本地內(nèi)存它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化之后的一個數(shù)據(jù)存放位置

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

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

            1. 當(dāng)線程修改變量時,會強制刷新到主內(nèi)存中
            2. 當(dāng)線程讀取變量時,會強制從主內(nèi)存讀取變量并且刷新到工作內(nèi)存中

          指令重排

          • 何為指令重排?

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

          我們來看下代碼


          //指令重排測試
          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兩個線程 他們分別執(zhí)行write()方法和 reader()方法,執(zhí)行的順序有可能如下圖所示

          • 問題分析: 如圖可見,A線程的L2和L1的執(zhí)行順序重排序了,如果要是這樣執(zhí)行的話,當(dāng)A執(zhí)行完L2時,B開始執(zhí)行L3,可是這個時候flag還是為false,那么L4就執(zhí)行不了了,所以result的值還是初始值0,沒有被改變?yōu)?1,導(dǎo)致程序執(zhí)行錯誤

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

          private volatile Integer number = 10;

          • 這個時候L1就一定在L2前面執(zhí)行

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

          內(nèi)存屏障

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

          1. LoadLoad屏障:

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

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

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

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

            • Store1 StoreStore Store2 確保Store1數(shù)據(jù)對其他處理器可見(刷新到內(nèi)存)先于Store2及所有后續(xù)存儲指令的存儲
          • 裝載load 就是讀 int a = load1  ( load1的裝載)
          • 存儲store就是寫     store1 = 5  ( store1的存儲)

          volatile與內(nèi)存屏障

          那么volatile和這四種內(nèi)存屏障又有什么關(guān)系呢,具體是怎么插入的呢?

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

            • 前面插入一個StoreStore屏障
            • 后面插入一個StoreLoad屏障
          2. volatile讀(只在后面插入屏障)

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

          官方提供的表格是這樣的

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

            this.flag = true// L1
            this.number = 20// L2

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

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

          所以L1,L2的執(zhí)行順序不被重排序


          ps:總部四號樓真是越來越好了,獎勵自己一杯奶茶


          更多精彩,請關(guān)注公眾號xhJaver,京東工程師和你一起成長

          往期精彩


          瀏覽 35
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天操夜 | 国产在线簧片 | 欧美少妇坐爱视频 | 91丨豆花丨成人熟女 熟女 | 成人视频二区三区 |