標籤:使用 子類 syn lock 線程 static 資料 false 並發
摘要
Volatile是Java提供的一種弱同步機制,當一個變數被聲明成volatile類型後編譯器不會將該變數的操作與其他記憶體操作進行重排序。在某些情境下使用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(); } }}
輸出:
643710
線程每次對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解決的是多個線程之間訪問資源的同步性
Java並發編程--Volatile詳解