最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java多線程之volatile關(guān)鍵字及內(nèi)存屏障實(shí)例解析

 更新時(shí)間:2019年05月31日 14:07:03   作者:老胡  
volatile是JVM提供的一種最輕量級(jí)的同步機(jī)制,因?yàn)镴ava內(nèi)存模型為volatile定義特殊的訪問(wèn)規(guī)則,使其可以實(shí)現(xiàn)Java內(nèi)存模型中的兩大特性:可見(jiàn)性和有序性。這篇文章主要介紹了Java多線程之volatile關(guān)鍵字及內(nèi)存屏障,需要的朋友可以參考下

前面一篇文章在介紹Java內(nèi)存模型的三大特性(原子性、可見(jiàn)性、有序性)時(shí),在可見(jiàn)性和有序性中都提到了volatile關(guān)鍵字,那這篇文章就來(lái)介紹volatile關(guān)鍵字的內(nèi)存語(yǔ)義以及實(shí)現(xiàn)其特性的內(nèi)存屏障。

volatile是JVM提供的一種最輕量級(jí)的同步機(jī)制,因?yàn)镴ava內(nèi)存模型為volatile定義特殊的訪問(wèn)規(guī)則,使其可以實(shí)現(xiàn)Java內(nèi)存模型中的兩大特性:可見(jiàn)性和有序性。正因?yàn)関olatile關(guān)鍵字具有這兩大特性,所以我們可以使用volatile關(guān)鍵字解決多線程中的某些同步問(wèn)題。

volatile的可見(jiàn)性

volatile的可見(jiàn)性是指當(dāng)一個(gè)變量被volatile修飾后,這個(gè)變量就對(duì)所有線程均可見(jiàn)。白話點(diǎn)就是說(shuō)當(dāng)一個(gè)線程修改了一個(gè)volatile修飾的變量后,其他線程可以立刻得知這個(gè)變量的修改,拿到最這個(gè)變量最新的值。

結(jié)合前一篇文章提到的Java內(nèi)存模型中線程、工作內(nèi)存、主內(nèi)存的交互關(guān)系,我們對(duì)volatile的可見(jiàn)性也可以這么理解,定義為volatile修飾的變量,在線程對(duì)其進(jìn)行寫入操作時(shí)不會(huì)把值緩存在工作內(nèi)存中,而是直接把修改后的值刷新回寫到主內(nèi)存,而當(dāng)處理器監(jiān)控到其他線程中該變量在主內(nèi)存中的內(nèi)存地址發(fā)生變化時(shí),會(huì)讓這些線程重新到主內(nèi)存中拷貝這個(gè)變量的最新值到工作內(nèi)存中,而不是繼續(xù)使用工作內(nèi)存中舊的緩存。

下面我列舉一個(gè)利用volatile可見(jiàn)性解決多線程并發(fā)安全的示例:

public class VolatileDemo {
 //private static boolean isReady = false;
 private static volatile boolean isReady = false;
 static class ReadyThread extends Thread {
  public void run() {
   while (!isReady) {    
   }
   System.out.println("ReadyThread finish");
  }
 }
 public static void main(String[] args) throws InterruptedException {
  new ReadyThread().start();
  Thread.sleep(1000);//sleep 1秒鐘確保ReadyThread線程已經(jīng)開(kāi)始執(zhí)行
  isReady = true;
 }
}

上面這段代碼運(yùn)行之后最終會(huì)在控制臺(tái)打印出: ReadyThread finish ,而當(dāng)你將變量isReady的volatile修飾符去掉之后再運(yùn)行則會(huì)發(fā)現(xiàn)程序一直運(yùn)行而不結(jié)束,而控制臺(tái)也沒(méi)有任何打印輸出。

我們分析下這個(gè)程序:初始時(shí)isReady為false,所以ReadyThread線程啟動(dòng)開(kāi)始執(zhí)行后,它的while代碼塊因標(biāo)志位isReady為false會(huì)進(jìn)入死循環(huán),當(dāng)用volatile關(guān)鍵字修飾isReady時(shí),main方法所在的線程將isReady修改為true之后,ReadyThread線程會(huì)立刻得知并獲取這個(gè)最新的isReady值,緊接著while循環(huán)就會(huì)結(jié)束循環(huán),所以最后打印出了相關(guān)文字。而當(dāng)未用volatile修飾時(shí),main方法所在的線程雖然修改了isReady變量,但ReadyThread線程并不知道這個(gè)修改,所以使用的還是之前的舊值,因此會(huì)一直死循環(huán)執(zhí)行while語(yǔ)句。

volatile的有序性

有序性是指程序代碼的執(zhí)行是按照代碼的實(shí)現(xiàn)順序來(lái)按序執(zhí)行的。

volatile的有序性特性則是指禁止JVM指令重排優(yōu)化。

我們來(lái)看一個(gè)例子:

public class Singleton {
 private static Singleton instance = null;
 //private static volatile Singleton instance = null;
 private Singleton() { }

 public static Singleton getInstance() {
  //第一次判斷
  if(instance == null) {
   synchronized (Singleton.class) {
    if(instance == null) {
     //初始化,并非原子操作
     instance = new Singleton(); 
    }
   }
  }
  return instance;
 }
}

上面的代碼是一個(gè)很常見(jiàn)的單例模式實(shí)現(xiàn)方式,但是上述代碼在多線程環(huán)境下是有問(wèn)題的。為什么呢,問(wèn)題出在instance對(duì)象的初始化上,因?yàn)?code> instance = new Singleton(); 這個(gè)初始化操作并不是原子的,在JVM上會(huì)對(duì)應(yīng)下面的幾條指令:

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

上面三個(gè)指令中,步驟2依賴步驟1,但是步驟3不依賴步驟2,所以JVM可能針對(duì)他們進(jìn)行指令重拍序優(yōu)化,重排后的指令如下:

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

這樣優(yōu)化之后,內(nèi)存的初始化被放到了instance分配內(nèi)存地址的后面,這樣的話當(dāng)線程1執(zhí)行步驟3這段賦值指令后,剛好有另外一個(gè)線程2進(jìn)入getInstance方法判斷instance不為null,這個(gè)時(shí)候線程2拿到的instance對(duì)應(yīng)的內(nèi)存其實(shí)還未初始化,這個(gè)時(shí)候拿去使用就會(huì)導(dǎo)致出錯(cuò)。

所以我們?cè)谟眠@種方式實(shí)現(xiàn)單例模式時(shí),會(huì)使用volatile關(guān)鍵字修飾instance變量,這是因?yàn)関olatile關(guān)鍵字除了可以保證變量可見(jiàn)性之外,還具有防止指令重排序的作用。當(dāng)用volatile修飾instance之后,JVM執(zhí)行時(shí)就不會(huì)對(duì)上面提到的初始化指令進(jìn)行重排序優(yōu)化,這樣也就不會(huì)出現(xiàn)多線程安全問(wèn)題了。

volatile使用場(chǎng)景

volatile的可以在以下場(chǎng)景中使用:

當(dāng)運(yùn)算結(jié)果不依賴變量當(dāng)前的值,或者能確保只有單一線程修改變量的值的時(shí)候,我們才可以對(duì)該變量使用volatile關(guān)鍵字
變量不需要與其他狀態(tài)變量共同參與不變約束

volatile與原子性

volatile關(guān)鍵字能保證變量的可見(jiàn)性和代碼的有序性,但是不能保證變量的原子性,下面我再舉一個(gè)volatile與原子性的例子:

public class VolatileTest {
 public static volatile int count = 0;

 public static void increase() {
  count++;
 }

 public static void main(String[] args) {
  Thread[] threads = new Thread[20];
  for(int i = 0; i < threads.length; i++) {
   threads[i] = new Thread(() -> {
    for(int j = 0; j < 1000; j++) {
     increase();
    }
   });
   threads[i].start();
  }
  //等待所有累加線程結(jié)束
  while (Thread.activeCount() > 1) {
   Thread.yield();
  }
  System.out.println(count);
 }
}

上面這段代碼創(chuàng)建了20個(gè)線程,每個(gè)線程對(duì)變量count進(jìn)行1000次自增操作,如果這段代碼并發(fā)正常的話,結(jié)果應(yīng)該是20000,但實(shí)際運(yùn)行過(guò)程中經(jīng)常會(huì)出現(xiàn)小于20000的結(jié)果,因?yàn)閏ount++這個(gè)自增操作不是原子操作。

上面的count++自增操作等價(jià)于count=count+1,所以JVM需要先讀取count的值,然后在count的基礎(chǔ)上給它加1,然后再將新的值重新賦值給count變量,所以這個(gè)自增總共需要三步。

 

上圖中我將線程對(duì)count的自增操作畫了個(gè)簡(jiǎn)單的流程,一個(gè)線程要對(duì)count進(jìn)行自增時(shí)要先讀取count的值,然后在當(dāng)前count值的基礎(chǔ)上進(jìn)行count+1操作,最后將count的新值重新寫回到count。

如果線程2在線程1讀取count舊值寫回count新值期間讀取count的值,顯然這個(gè)時(shí)候線程2讀取的是count還未更新的舊值,這時(shí)兩個(gè)線程是對(duì)同一個(gè)值進(jìn)行了+1操作,這樣這兩個(gè)線程就沒(méi)有對(duì)count實(shí)現(xiàn)累加效果,相反這些操作卻又沒(méi)有違反volatile的定義,所以這種情況下使用volatile依然會(huì)存在多線程并發(fā)安全的問(wèn)題。

volatile與內(nèi)存屏障

前面介紹了volatile的可見(jiàn)性和有序性,那JVM到底是如何為volatile關(guān)鍵字實(shí)現(xiàn)的這兩大特性呢,Java內(nèi)存模型其實(shí)是通過(guò)內(nèi)存屏障(Memory Barrier)來(lái)實(shí)現(xiàn)的。

內(nèi)存屏障其實(shí)也是一種JVM指令,Java內(nèi)存模型的重排規(guī)則會(huì)要求Java編譯器在生成JVM指令時(shí)插入特定的內(nèi)存屏障指令,通過(guò)這些內(nèi)存屏障指令來(lái)禁止特定的指令重排序。

另外內(nèi)存屏障還具有一定的語(yǔ)義:內(nèi)存屏障之前的所有寫操作都要回寫到主內(nèi)存,內(nèi)存屏障之后的所有讀操作都能獲得內(nèi)存屏障之前的所有寫操作的最新結(jié)果(實(shí)現(xiàn)了可見(jiàn)性)。因此重排序時(shí),不允許把內(nèi)存屏障之后的指令重排序到內(nèi)存屏障之前。

下面的表是volatile有關(guān)的禁止指令重排的行為:

第一個(gè)操作 第二個(gè)操作:普通讀寫 第二個(gè)操作:volatile讀 第二個(gè)操作:volatile寫
普通讀寫 可以重排 可以重排 不可以重排
volatile讀 不可以重排 不可以重排 不可以重排
volatile寫 可以重排 不可以重排 不可以重排

從上面的表我們可以得出下面這些結(jié)論:

當(dāng)?shù)诙€(gè)操作volatile寫時(shí),不論第一個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則保證了volatile寫之前的操作不會(huì)被重排到volatile寫之后。

當(dāng)?shù)谝粋€(gè)操作為volatile讀時(shí),不論第二個(gè)操作是什么,都不能重排。這個(gè)操作保證了volatile讀之后的操作不會(huì)被重排到volatile讀之前。

當(dāng)?shù)谝粋€(gè)操作為volatile寫,第二個(gè)操作為volatile讀時(shí),不能重排。

JVM中提供了四類內(nèi)存屏障指令:

屏障類型 指令示例 說(shuō)明
LoadLoad Load1; LoadLoad; Load2 保證load1的讀取操作在load2及后續(xù)讀取操作之前執(zhí)行
StoreStore Store1; StoreStore; Store2 在store2及其后的寫操作執(zhí)行前,保證store1的寫操作已刷新到主內(nèi)存
LoadStore Load1; LoadStore; Store2 在stroe2及其后的寫操作執(zhí)行前,保證load1的讀操作已讀取結(jié)束
StoreLoad Store1; StoreLoad; Load2 保證store1的寫操作已刷新到主內(nèi)存之后,load2及其后的讀操作才能執(zhí)行

總結(jié)

volatile實(shí)現(xiàn)了Java內(nèi)存模型中的可見(jiàn)性和有序性,它的這兩大特性則是通過(guò)內(nèi)存屏障來(lái)實(shí)現(xiàn)的,同時(shí)volatile無(wú)法保證原子性。

以上所述是小編給大家介紹的Java多線程之volatile關(guān)鍵字及內(nèi)存屏障實(shí)例解析,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

最新評(píng)論

娱乐| 永胜县| 河源市| 德钦县| 东安县| 安国市| 那曲县| 龙井市| 易门县| 呼和浩特市| 西吉县| 西畴县| 吴桥县| 上犹县| 安平县| 诏安县| 江油市| 沙河市| 辉南县| 南靖县| 沅江市| 平南县| 塔河县| 枞阳县| 新蔡县| 佛冈县| 柏乡县| 红桥区| 密云县| 彩票| 乌拉特中旗| 竹溪县| 吉首市| 济南市| 铅山县| 普兰店市| 四平市| 博客| 峨眉山市| 吐鲁番市| 武川县|