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線程的內存分配講起,先來看一張(原文鏈接:http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

     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編程思想第四版

   http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章