Java併發編程--Volatile詳解

Volatile特性
  1.可見性:當一條線程對volatile變量進行了修改操做時,其餘線程能當即知道修改的值,即當讀取一個volatile變量時老是返回最近一次寫入的值
  2.原子性:對於單個voatile變量其具備原子性(能保證long double類型的變量具備原子性),但對於i ++ 這類複合操做其不具備原子性(見下面分析)
 
使用volatile變量的前提
  1.對變量的寫入操做不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值
  2.該變量不會與其餘狀態變量一塊兒歸入不變性條件中
  3.在訪問變量時不須要加鎖
 
volatile可見性
volatile的可見性正是基於happend -before(先行發生)關係實現的。
 
happend-before:java內存模型有八條能夠保證happend-before的規則(詳見《深刻理解Java虛擬機》P376),若是兩個操做之間的關係沒法從這八條規則中推導出來的話,它們就沒有順序保障,虛擬機就能夠對它們隨意地進行重排序.
 
其中就包含」volatile變量規則「:對一個volatile變量的寫操做先行發生於後面對這個變量的讀操做,此規則保證虛擬機不會對volatile讀/寫操做進行重排序。
 
經過一個例子來了解vloative的可見性
例1:
public class VolatileTest extends  Thread{
    private boolean isRunning = true;
    public boolean isRunning(){
        return isRunning;
    }
    public void setRunning(boolean isRunning){
        this.isRunning= isRunning;
    }
    public void run(){
        System.out.println("進入了run...............");
        while (isRunning){}
        System.out.println("isUpdated的值被修改成爲false,線程將被中止了");
    }
    public static void main(String[] args) throws InterruptedException {
        VolatileTest volatileThread = new VolatileTest();
        volatileThread.start();
        Thread.sleep(1000);
        volatileThread.setRunning(false);   //中止線程
    }
}
輸出:
進入了run...............
發現並無輸出」isUpdated的值被修改成爲false,線程將被中止了」這一句,說明經過setRunning來修改isRunning的值對於該程序是不可見的,也就是說程序不知道本身的值被修改了,爲何?
 
     緣由:Java內存模型(JMM)規定了全部的變量都存儲在 主內存中,主內存中的變量爲共享變量,而每條線程都有本身的 工做內存,線程的工做內存保存了從主內存拷貝的變量,全部對變量的操做都在本身的工做內存中進行,完成後再刷新到主內存中,回到例1,第18行號代碼主線程(線程main)雖然對isRunning的變量進行了修改且有刷新回主內存中( 《深刻理解java虛擬機》中關於主內存與工做內存的交互協議提到變量在工做內存中改變後必須將該變化同步回主內存),但volatileThread線程讀的還是本身工做內存的舊值致使出現多線程的可見性問題,解決辦法就是給isRunning變量加上volatile關鍵字。
 
      當變量被聲明成volatile類型後,線程對該變量進行修改後會當即刷新回主內存,而其餘線程讀取該變量時會先將本身工做內存中的變量置爲無效,再從主內存從新讀取變量到本身的工做內存,這樣就避免發生線程可見性問題。
 
volatile內存語義總結以下
1.當線程對volatile變量進行寫操做時,會將修改後的值刷新回主內存
 
2.當線程對volatile變量進行讀操做時,會先將本身工做內存中的變量置爲無效,以後再經過主內存拷貝新值到工做內存中使用。
 
volatile原子性
   volatile並不徹底具備原子性,對於複合操做其仍存在線程不安全的問題,如
例2
public class VolatileTest1{
    private volatile  int value;  //將value變量聲明成volatile類型
    public  void increment(){
         value ++;   
         System.out.println(value);   
    }
    public static void main(String[] args) {
        final VolatileTest1 volatileTest1 = new VolatileTest1();
        for(int i = 0; i < 10; i ++){
            new Thread(new Runnable() {
                public void run() {
                     volatileTest1.increment();
                }
            }).start();
        }
    }
}
輸出:
1 6 5 4 2 3 8 7 9 10
線程每次對value進行自增操做,顯然輸出結果不是咱們想要的那種,這裏就出現了線程安全問題,爲何?
  像value ++這樣的操做並不具備原子性,其實際的過程以下:
當線程1在步驟2對value進行計算時,恰好其餘線程也對value進行了修改,這時線程1返回的值就不是咱們指望的值了,因而出現線程安全問題,因此volatile不能保證複合操做具備原子性;解決辦法就是給increment方法加鎖(lock/synchronized)或將變量聲明爲原子類類型。
 
Synchronized與volatile區別 
1.volatile只能修飾變量,而synchronized能夠修改變量,方法以及代碼塊
 
2.volatile在多線程中不會存在阻塞問題,synchronized會存在阻塞問題
 
3.volatile能保證數據的可見性,但不能徹底保證數據的原子性,synchronized即保證了數據的可見性也保證了原子性
 
4.volatile解決的是變量在多個線程之間的可見性,而sychroized解決的是多個線程之間訪問資源的同步性
相關文章
相關標籤/搜索