一、基本概念 volatile作為Java虛擬機提供的最輕量級同步機制,用於保證共用變數在多線程的情況下各線程獲取相同,不出現對該變數的操作和其他記憶體操作一樣重排序。 重排序 在虛擬機上,由於記憶體操作速度遠小於CPU的操作速度,為了減少CPU在等待記憶體操作過程的時間,虛擬機會按照一定規則打亂指令的執 ...
一、基本概念
volatile作為Java虛擬機提供的最輕量級同步機制,用於保證共用變數在多線程的情況下各線程獲取相同,不出現對該變數的操作和其他記憶體操作一樣重排序。
重排序
在虛擬機上,由於記憶體操作速度遠小於CPU的操作速度,為了減少CPU在等待記憶體操作過程的時間,虛擬機會按照一定規則打亂指令的執行順序,
int a = 1;
int b = 0;
int c = a+b;
這三行除了第三行需要在前兩行之後執行,前兩行在虛擬機中的執行順序是隨機的,這就是在保證一定順序情況下的指令重排序。《深入理解Java虛擬機》的12節有更深入解釋,可以看一下。
而對於volatile來說,其中有一個規則是對於它同步機制的保證,就是volatile修飾的變數,寫操作先行發生於後面對於這個變數的讀操作,意思是在出現讀取變數和寫入變數操作時候,寫入優先於讀取,具體會在下麵解釋。
記憶體模型
由於volatile主要是對於記憶體運用,需要簡單解釋一下Java併發過程中對記憶體模型的三個性質以及在volatile的體現。
可見性
可見性保證共用變數在多線程情況下值的準確性。指當一個線程對共用變數進行修改操作的時候,其他對該變數有操作的線程可以立即獲取這個修改。volatile使用的方式是在修改的同時立即同步到主記憶體中並將其他記憶體中的變數值無效化,使用之前需要再次從主記憶體中獲取。
原子性
原子性只要指對於原子性變數的load、read、assign、use、store和write保證原子性,這裡指基本數據類型的訪問讀寫。
有序性
由於在虛擬機中存在指令重排序的問題,因此存在synchronize和volatile關鍵詞來保證部分操作禁止指令重排序。
二、實現原理
在大致瞭解這個關鍵詞的作用之後,需要關註的就是它的實現原理,它是如何保證共用變數的同步的。
記憶體模型
被volatile修飾的共用變數在多線程的情況下變數記憶體使用模型如圖
共用變數會被創建在主記憶體中,也就是多線程的公共存儲區,而多個線程在訪問該變數的時候是將其先從記憶體拷貝變數到CPU緩存中,在對該值進行操作。同時該變數會有兩個特性:
- 多線程的可見性,如上面所說,使用共用變數的線程對變數的值進行操作之後,CPU緩存立即同步到主記憶體中,並將其他線程在CPU緩存的變數值無效化,強制讓其他線程再次讀取主記憶體的值,實現同步機制。
- 禁止指令重排序,如上面所說,帶有volatile的變數寫操作先行發生於後面對於這個變數的讀操作,多個線程操作變數時,優先處理寫操作。
實現機制
在加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock首碼指令,對變數所在記憶體加上記憶體屏障(作用是保證編譯器在記憶體訪問上有序),保證變數操作之前的操作在虛擬機中一定在之前執行,同時保證變數的修改立即會同步到主記憶體上。
缺點
其實大家也註意到了,volatile並不能保證變數的原子性,它保證的是對變數的讀取和寫入的原子性,但是對於中間變數的操作並不能保證,如下麵的例子
public class VolatileTest {
public volatile int i = 0;
//函數調用,增加操作時間
public void add(){
i++;
}
public static void main(String[] args) {
final VolatileTest test = new VolatileTest();
for(int j = 0;j<10;j++){
new Thread(){
@Override
public void run() {
for(int j = 0;j<1000;j++)
test.add();
}
}.start();
}
System.out.println(test.i);
}
}
正常來說結果應該是10000,但是每次結果都小於10000,因為在自增的過程不是同步的,假設a線程讀取了i值為1,進入i+1的操作時,b線程也讀取了i,此時並無寫入操作,i任然是1,之後a線程加1結束寫入主記憶體2,但是由於b線程已經在加1操作,寫入操作懟它無影響,結果還是2,寫入主記憶體之後還是2,但是實際上,此時應該為3。因此,volatile逐漸被淘汰減少使用了。