1. 程式人生 > >volatile變數型別的詳細說明

volatile變數型別的詳細說明

關鍵字volatile是java虛擬機器提供的最輕量級的同步機制。但是許多人對它的理解還是不正確,不完整。

下面詳細講解一下:

由於關鍵字volatile關鍵沒有被更好的理解,因此許多程式設計師遇到處理多執行緒資料競爭問題的時候都是有synchronzied來同步,僅為讀寫一兩個例項域就使用同步,顯得開銷有些大了。因此需要深入理解此關鍵字。

Java記憶體模型對volatile專門定義了一些特殊的訪問規則

當一個變數被定義為volatile時,此變數具備兩種特性:

1.可見性:可見性在java記憶體模型中有定義,可以參看。

普通變數則沒有,他們線上程之間的互動是通過主記憶體來完成,volatile變數則是通過主記憶體完成交換,但是兩者區別在於volatile變數能立即同步到主記憶體中,當一個執行緒修改變數的變數的時候,立刻會被其他執行緒感知到。

特別注意一點:volatile變數的可見性經常性被誤解,認為,valotile變數在各個執行緒中是一致的。所以基於volatile變數是安全的。這種認為是錯誤的。論據是正確的,但是得出的是安全的就不正確了。不會存在不一致性問題(在各個的工作記憶體中可以存在不一致的情況,但是由於每次使用之前都要重新整理,執行引擎看不到不一致的問題,因此認為不存在不一致的問題)但是java裡面的運算中並非原子操作,導致volatile變數的運算在併發下一樣不安全。

實現可見性方式: 1.volatile   2.synchronized    3.final

下面程式碼演示不安全的情況:

public class VolatileTest {

private static final int THREAD_NUM =20;

public static volatile int num= 0;

public static void increase(){

num++ ;

}

public static void main(String[] args) {

Thread[] threads = new Thread[THREAD_NUM];

for (int i = 0; i < THREAD_NUM; i++) {

threads[i] = new Thread(new Runnable() {

@Override

public void run() {

for(int i=0;i<1000;i++){

increase();

}

}

});

threads[i].start();

}

while(Thread.activeCount()>1){

Thread.yield();

}

System.out.println(num);

}

}

輸出的正確答案應該是200000,但是每次輸出都小於200000,問題在於increase()方法。我們用javap發編譯看一下發現就increase()方法在Class中檔案有四條位元組碼組成。但是這種位元組碼測試並不能說明這條指令是原子操作,因為一條位元組碼指令解釋執行時,直譯器將要執行許多行程式碼才能實現它的語義,當編譯執行時,一條也可能被住轉化為若干本地機器指令。
當不符合一下規則是還是使用synchronized或java.util.concurrent中的原子類。
1.運算結果並不依賴變數的當前值,或者能夠確保單一的執行緒修改變數的

2.變數不需要與其他的狀態變數共同參與不變約束。

2.禁止指令重排序優化

普通變數僅僅會保證在該方法的執行過程中所依賴複製結果的地方都能獲取到正確的結果,而不能保證變數複製操作的順序與程式程式碼中的執行順序一致。