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