1. 程式人生 > >Java之Volatile關鍵字使用

Java之Volatile關鍵字使用

1.為什麼要使用Volatile關鍵字?

  先來看看一段程式碼:

package com.zy;
 
importjava.util.concurrent.TimeUnit;
 
public class VolatileTest {
   private static boolean isRuning = true;
   public static void main(String[] args) {
       new Thread(new Runnable() {
          @Override
          public void run() {
              int i = 0;
              while(VolatileTest.isRuning){
                 i++;
              }
          }
       }).start();
       try {
          TimeUnit.SECONDS.sleep(1);
       } catch(InterruptedException e) {
          e.printStackTrace(); 
       }
       new Thread(new Runnable() {
          @Override
          public void run() {
              isRuning = false;  //設定is為false,使上面的執行緒結束while迴圈
              System.out.println("設定isRuning為:false");
          }
       }).start();
   }
}


      程式碼很簡單,啟動兩個執行緒,在主類中定一個一個全域性的成員變數isRuning,執行緒一啟動只要isRunning為true將持續i++,執行緒2將isRunning置為false,按照正常邏輯此時執行緒1也將停止,因為while中條件不成立了嘛。然而真是這樣嗎?

      答案顯然是:NO,整個程式依然在執行。

相信這種程式碼肯定有很多人寫過,犯過這種錯的人也不再少數。廢話不多說,那麼究竟是為什麼會發生這種情況呢,我只給出比較淺顯的解釋,學藝有限,不再深究。

     Java記憶體模型的主要目標是定義程式中各個變數的訪問規則,即在虛擬機器中將變數儲存到記憶體和從記憶體中取出變數這樣底層細節。此處的變數與Java程式設計時所說的變數不一樣,指包括了例項欄位、靜態欄位和構成陣列物件的元素,但是不包括區域性變數與方法引數,後者是執行緒私有的,不會被共享。

     Java記憶體模型中規定了所有的變數都儲存在主記憶體中,每條執行緒還有自己的工作記憶體(可以與前面將的處理器的快取記憶體類比),執行緒的工作記憶體中儲存了該執行緒使用到的變數到主記憶體副本拷貝,執行緒對變數的所有操作(讀取、賦值)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數。不同執行緒之間無法直接訪問對方工作記憶體中的變數,執行緒間變數值的傳遞均需要在主記憶體來完成,執行緒、主記憶體和工作記憶體的互動關係如下圖所示,和上圖很類似。

相信讀了上面一段話大家也就大概明白了,執行緒中獲取到所有的變數(示例程式中的isRunning)實際上只主記憶體的一個副本,執行緒2改變isRunning的值只是改變了副本的值,此時執行緒1如果要發現isRunning的值改變,首先執行緒2要將isRunning的值重新整理的主記憶體,然後執行緒1要重新重新整理isRunning的值。

至於執行緒中的變數副本的值如何重新整理和何時重新整理到主記憶體,和如何和何時從主記憶體中重新load值,本文不做概述。

所以說不同執行緒中的變數是不可見的!!!

2. Volatile作用

     此時Volatile關鍵字出場了,它正是用來解決不同執行緒變數的可視性問題的。

     如果你將一個變數申明Volatile的,那麼只要對這個變數做出更改,那麼其他的所有讀操作就會看到這個更改。即使使用了本地快取,情況也是如此,volatile所修飾的變數會立即被寫入到主記憶體,而讀操作就發生在主記憶體中。

3.Volatile使用場景

     多個執行緒同時訪問某個變數,那麼這個變數就應該是volatile的,否則這個變數只能用Synchronize來同步訪問。如果一個方法或者程式碼塊完全是synchronize的,那麼就不要volatile來修飾,因為同步方法資料會立即寫入主記憶體,同時方法取值也是直接在主記憶體中取。

4.使用條件

        4.1:一個變數值不依賴於它之前的值。比如:遞增的計數器,++i

        4.2:這個值不受其他變數值得限制。比如:Range類中的lower和upper邊界必須遵守lower<upper

        以上條件應該是要保證volatile變數的操作是原子操作。(何為原子操作:原子操作是不能被執行緒排程機制中斷的操作,一旦操作開始,那麼它一定可以在可能發生上下文切換之前(切換到其他執行緒之前)完成操作。)如果不是原子操作的話在完成一個操作中間可能包含多個指令,而這中間就可能發生上下文切換。在切換時其中的值已經被其他任務修改,此時再切換回來執行最後一條指令,所讀取到的變數值可能已經被其他任務修改過。

 如有不足之處請指出,菜鳥一枚,輕噴。微笑微笑

參考資料:

   java程式設計思想第四版