在java中線程併發中,線程之間通訊方式分爲兩種:共享內存和消息傳遞。共享內存指的是多個線程之間共享內存的屬性狀態;消息傳遞指的是線程之間發送信息來通訊。在介紹volatile,咱們先了解一下共享內存一些基本概念。java
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可以保證程序的原子性,例如將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;
結果符合預期。