<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>

          徹底理解volatile關(guān)鍵字

          共 6314字,需瀏覽 13分鐘

           ·

          2021-09-03 20:41

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          徹底理解volatile關(guān)鍵字

          volatile關(guān)鍵字是由JVM提供的最輕量級(jí)同步機(jī)制。與被濫用的synchronized不同,我們并不習(xí)慣使用它。想要正確且完全的理解它并不容易。

          Java內(nèi)存模型

          Java內(nèi)存模型由Java虛擬機(jī)規(guī)范定義,用來屏蔽各個(gè)平臺(tái)的硬件差異。簡(jiǎn)單來說:

          • 所有變量?jī)?chǔ)存在主內(nèi)存。

          • 每條線程擁有自己的工作內(nèi)存,其中保存了主內(nèi)存中線程使用到的變量的副本。

          • 線程不能直接讀寫主內(nèi)存中的變量,所有操作均在工作內(nèi)存中完成。

          線程,主內(nèi)存,工作內(nèi)存的交互關(guān)系如圖。

          內(nèi)存間的交互操作有很多,和volatile有關(guān)的操作為:

          • read(讀取):作用于主內(nèi)存變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用

          • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。

          • use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。

          • assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。

          • store(存儲(chǔ)):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write的操作。

          • write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中。

          對(duì)被volatile修飾的變量進(jìn)行操作時(shí),需要滿足以下規(guī)則:

          • 規(guī)則1:線程對(duì)變量執(zhí)行的前一個(gè)動(dòng)作是load時(shí)才能執(zhí)行use,反之只有后一個(gè)動(dòng)作是use時(shí)才能執(zhí)行l(wèi)oad。線程對(duì)變量的read,load,use動(dòng)作關(guān)聯(lián),必須連續(xù)一起出現(xiàn)。-----這保證了線程每次使用變量時(shí)都需要從主存拿到最新的值,保證了其他線程修改的變量本線程能看到。

          • 規(guī)則2:線程對(duì)變量執(zhí)行的前一個(gè)動(dòng)作是assign時(shí)才能執(zhí)行store,反之只有后一個(gè)動(dòng)作是store時(shí)才能執(zhí)行assign。線程對(duì)變量的assign,store,write動(dòng)作關(guān)聯(lián),必須連續(xù)一起出現(xiàn)。-----這保證了線程每次修改變量后都會(huì)立即同步回主內(nèi)存,保證了本線程修改的變量其他線程能看到。

          • 規(guī)則3:有線程T,變量V、變量W。假設(shè)動(dòng)作A是T對(duì)V的use或assign動(dòng)作,P是根據(jù)規(guī)則2、3與A關(guān)聯(lián)的read或write動(dòng)作;動(dòng)作B是T對(duì)W的use或assign動(dòng)作,Q是根據(jù)規(guī)則2、3與B關(guān)聯(lián)的read或write動(dòng)作。如果A先與B,那么P先與Q。------這保證了volatile修飾的變量不會(huì)被指令重排序優(yōu)化,代碼的執(zhí)行順序與程序的順序相同。

          使用volatile關(guān)鍵字的特性

          1.被volatile修飾的變量保證對(duì)所有線程可見。

          由上文的規(guī)則1、2可知,volatile變量對(duì)所有線程是立即可見的,在各個(gè)線程中不存在一致性問題。那么,我們是否能得出結(jié)論:volatile變量在并發(fā)運(yùn)算下是線程安全的呢?
          這確實(shí)是一個(gè)非常常見的誤解,寫個(gè)簡(jiǎn)單的例子:

          public class VolatileTest extends Thread{
              static volatile int increase = 0;
              static AtomicInteger aInteger=new AtomicInteger();//對(duì)照組
              static void increaseFun() {
                  increase++;
                  aInteger.incrementAndGet();
              }
              public void run(){
                  int i=0;
                  while (i < 10000) {
                      increaseFun();
                      i++;
                  }
              }
              public static void main(String[] args) {
                  VolatileTest vt = new VolatileTest();
                  int THREAD_NUM = 10;
                  Thread[] threads = new Thread[THREAD_NUM];
                  for (int i = 0; i < THREAD_NUM; i++) {
                      threads[i] = new Thread(vt, "線程" + i);
                      threads[i].start();
                  }
                  //idea中會(huì)返回主線程和守護(hù)線程,如果用Eclipse的話改為1
                  while (Thread.activeCount() > 2) {
                      Thread.yield();
                  }
                  System.out.println("volatile的值: "+increase);
                  System.out.println("AtomicInteger的值: "+aInteger);
              }
          }



          這個(gè)程序我們跑了10個(gè)線程同時(shí)對(duì)volatile修飾的變量進(jìn)行10000的自增操作(AtomicInteger實(shí)現(xiàn)了原子性,作為對(duì)照組),如果volatile變量是并發(fā)安全的話,運(yùn)行結(jié)果應(yīng)該為100000,可是多次運(yùn)行后,每次的結(jié)果均小于預(yù)期值。顯然上文的說法是有問題的。

          volatile修飾的變量并不保值原子性,所以在上述的例子中,用volatile來保證線程安全不靠譜。我們用Javap對(duì)這段代碼進(jìn)行反編譯,為什么不靠譜簡(jiǎn)直一目了然:

          getstatic指令把increase的值拿到了操作棧的頂部,此時(shí)由于volatile的規(guī)則,該值是正確的。
          iconst_1和iadd指令在執(zhí)行的時(shí)候increase的值很有可能已經(jīng)被其他線程加大,此時(shí)棧頂?shù)闹颠^期。
          putstatic指令接著把過期的值同步回主存,導(dǎo)致了最終結(jié)果較小。
          volatile關(guān)鍵字只保證可見性,所以在以下情況中,需要使用鎖來保證原子性:

          • 運(yùn)算結(jié)果依賴變量的當(dāng)前值,并且有不止一個(gè)線程在修改變量的值。

          • 變量需要與其他狀態(tài)變量共同參與不變約束

          那么volatile的這個(gè)特性的使用場(chǎng)景是什么呢?

          • 模式1:狀態(tài)標(biāo)志

          • 模式2:獨(dú)立觀察(independent observation)

          • 模式3:“volatile bean” 模式

          • 模式4:開銷較低的“讀-寫鎖”策略

          具體場(chǎng)景請(qǐng)大家查看這位大神的博客

          2.禁止指令重排序優(yōu)化。

          由上文的規(guī)則3可知,volatile變量的第二個(gè)語義是禁止指令重排序。指令重排序是什么?簡(jiǎn)單點(diǎn)說就是
          jvm會(huì)把代碼中沒有依賴賦值的地方打亂執(zhí)行順序,由于一些規(guī)則限定,我們?cè)趩尉€程內(nèi)觀察不到打亂的現(xiàn)象(線程內(nèi)表現(xiàn)為串行的語義),但是在并發(fā)程序中,從別的線程看另一個(gè)線程,操作是無序的。
          一個(gè)非常經(jīng)典的指令重排序例子:

          public class SingletonTest {
              private volatile static SingletonTest instance = null;
              private SingletonTest() { }
              public static SingletonTest getInstance() {
                  if(instance == null) {
                      synchronized (SingletonTest.class){
                          if(instance == null) {
                              instance = new SingletonTest();  //非原子操作
                          }
                      }
                  }
                  return instance;
              }
          }

          這是單例模式中的“雙重檢查加鎖模式”,我們看到instance用了volatile修飾,由于 instance = new SingletonTest();可分解為:

          1.memory =allocate(); //分配對(duì)象的內(nèi)存空間
          2.ctorInstance(memory); //初始化對(duì)象
          3.instance =memory; //設(shè)置instance指向剛分配的內(nèi)存地址

          操作2依賴1,但是操作3不依賴2,所以有可能出現(xiàn)1,3,2的順序,當(dāng)出現(xiàn)這種順序的時(shí)候,雖然instance不為空,但是對(duì)象也有可能沒有正確初始化,會(huì)出錯(cuò)。

          總結(jié)

          并發(fā)三特征可見性和有序性和原子性中,volatile
          通過新值立即同步到主內(nèi)存和每次使用前從主內(nèi)存刷新機(jī)制保證了可見性。
          通過禁止指令重排序保證了有序性。
          無法保證原子性。
          而我們知道,synchronized關(guān)鍵字
          通過lock和unlock操作保證了原子性,
          通過對(duì)一個(gè)變量unlock前,把變量同步回主內(nèi)存中保證了可見性,
          通過一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作保證了有序性。
          他的“萬能”也間接導(dǎo)致了我們對(duì)synchronized關(guān)鍵字的濫用,越泛用的控制,對(duì)性能的影響也越大,雖然jvm不斷的對(duì)synchronized關(guān)鍵字進(jìn)行各種各樣的優(yōu)化,但是我們還是要在合適的時(shí)候想起volatile關(guān)鍵字啊,哈哈哈哈。


            作者 |  fumitzuki

          來源 |  csdn.net/fumitzuki/article/details/81630048


          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  无码欧美日韩二区三区蜜桃 | 日韩一区二区三区操b | 久久久久久久久久免费视频 | 欧美午夜精品福利 | 一级大香蕉网站视频 |