淺析Volatile關鍵字

淺析Volatile關鍵字

在java中線程併發中,線程之間通訊方式分爲兩種:共享內存和消息傳遞。共享內存指的是多個線程之間共享內存的屬性狀態;消息傳遞指的是線程之間發送信息來通訊。在介紹volatile,咱們先了解一下共享內存一些基本概念。java

JMM

Java內存模型(簡稱JMM)控制線程通訊,能夠分爲主內存和本地內存,每一個線程擁有一個本地內存。編程

 

如圖,通常主存只有一個,根據線程數不一樣,本地內存(又稱工做內存)數目也不一樣,本地內存是抽象出來的概念,實際上不存在。數據結構

線程之間經過內存來同步狀態能夠分爲如下幾個步驟:多線程

一、  線程1從主存獲取變量x=1,假設此時其餘線程x都爲1併發

 

二、  線程1將本地內存中x值修改成2app

三、  線程1將本地內存中x值放回主存ide

四、  其餘線程同步主存數據spa

以上,就是理想狀態線程之間經過主存通訊狀態,保證了內存的可見性線程

 

指令重排序

         java程序在運行時,並不會嚴格按照程序代碼編寫的順序執行代碼,會對其進行重排序。例如如下例子:3d

 

         在程序執行代碼的時候,步驟1和步驟2可能被重排序,致使2可能在1以前先執行,可是重排序的前提是在單線程狀況下不會改變運行結果,所以1和2能夠重排序,可是3引用了1和2中的變量,所以3不會被重排序到1或者2以前。在單線程狀況下,重排序不會影響,但在多線程中會致使結果不可預見,可使用其餘方法來保證有序性

原子性操做

         前面提到了可見性有序性原子性是在併發編程中最常碰見的三個概念。原子性指不可分割的操做。舉例說明,在32爲JVM中長度小於32爲的數據讀寫都是原子性操做,可是64位數據結構的讀寫不是原子性操做。例子以下:

一、  線程1和2中有共同變量x,爲Long型

二、  線程1將x改成LONG_MAX/2

三、線程1在修改本地內存中的值到主存時,先將Long的前32保存到主存中,此時主存中的x值爲LONG_MAX/2的前32爲加上LONG_MAX後32爲,咱們設爲z

 

四、線程2讀取主存中的x值並更新

五、線程1同步後32位數據到主存中,以後線程3也同步主存數據

         最後能夠看到,因爲64爲數據讀寫不是原子性的,單隻線程2獲取的數據爲錯誤數據,若是,線程1同步主存的操做爲原子性,那麼就不會出現以上狀況。

 

Volatile關鍵字

Volatile可以保證程序的原子性,例如將Long或者Double設置爲volatile能夠避免因爲非原子性讀寫形成的數據不一樣步。可是,相似於x = x+1;這種類型的複合操做,volatile依舊服務保證原子性。

一、  volatile關鍵字x,線程1將x修改成LONG_MAX/2,主存爲z

二、此時,因爲x是volatile,所以線程要從主存讀取這個值的時候,並不會主動到主存中獲取,而是等到其餘線程通知他再去獲取。所以,線程1將完整的x值寫到主存中。

三、以後,通知其餘線程去獲取x的值,以實現數值同步,保證原子性

         因爲volatile的值會及時將值刷新到主存中,volatile也保證了可見性

         那麼volatile是否能保證有序性?我的看法是能在必定程度上保證數據的可見性,可是沒有了解到有資料明確說明volatile保證了原子性。

         volatile實際上設置了內存屏障,因爲內存屏障的存在,保證了多線程下不會因爲重排序致使數據不一致的問題。

是否重排序

第二個操做

第一個操做

普通讀寫

Volatile讀

Volatile寫

普通讀寫

 

 

No

Volatile讀

No

No

No

Volatile寫

 

No

No

可知volatile寫以前的操做不會被重排序到以後;volatile讀以後的操做不會被重排序到以前;第一個操做是volatile寫,第二個操做是volatile讀不會重排序。

         JMM採起的內存屏障策略爲:

一、  在volatile寫前面插入StoreStore屏障

二、  在volatile寫後面插入StoreLoad屏障

三、  在volatile讀後面插入LoadLoad屏障

四、  在volatile讀後面插入LoadStore屏障

 

StoreStore屏障保證volatile寫以前的普通寫刷新到主存當中,避免與前面普通寫重排序;

StoreLoad屏障保證volatile寫不會和以後的volatile讀寫重排序;

LoadLoad屏障禁止volatile讀不會與下面的普通讀重排序;

LoadStore屏障禁止volatile讀不會與下面的普通寫重排序。

具體可見p1,p2

 

P1

 

P2

 

一個例子

         以下例子,線程1執行writer方法,線程2執行reader方法,因爲JVM會進行重排序,所以可能的執行順序爲:

         一、線程1執行步驟2,此時a的值仍是0

         二、線程2執行步驟3,由於重排序不會在引發單線程結果改變,所以不會先執行4

         三、線程2執行步驟4,i的值爲0

         四、線程1執行步驟1

 

Int a = 0
Boolean flag =false
public void writer(){
  a = 1;        //1
  flag = true;   //2
}
public void reader(){
  if(flag){      //3
     int i = a;  //4
  }
}

可見,因爲重排序致使結果改變,修改代碼以下:

Int a = 0
volatile boolean flag =false
public void writer(){
    a = 1;        //1
    flag = true;   //2
}
public void reader(){
    if(flag){      //3
    int i = a;  //4
    }
}

執行步驟以下:

一、  線程1執行1,由於volatile變量flag前面插入StoreStore屏障,不會重排序

二、  線程1執行2;

三、  線程2執行3;

四、  線程2執行4,i爲2;

結果符合預期。

相關文章
相關標籤/搜索