java-多線程深入(四)Volatile分析,java-volatile
(一)volatile的使用
1、使用情境
(1)狀態標識。用於即時指示某個重要性事件的發生,比如完成初始化或者停機。
volatile boolean toShutdown; ...... public void shutdown() { toShutdown = true; } public void doWork() { while (!toShutdown) { // 執行操作 } }(2)對象安全發布。volatile是處理對象安全發布的其中一種方式,更多內容參加這裡。
class Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }
經典的雙重檢查單例模式,volatile變數不會導致new對象的時候對象未初始化完成。
2、通常來說,使用volatile必須具備以下2個條件:
(1)對變數的寫操作不依賴於當前值
(2)該變數沒有包含在具有其他變數的不變式中
對於1,因為volatile變數不保證原子性,一個線程執行i++的同時另一個線程擷取i的值,不一定能擷取到最新值。
對於2,
volatile int min = 0; volatile int max = 10; public boolean setA(int newMin) { if (newMin > max) { return false; } min = newMin; return true; } public boolean setB(int newMax) { if (newMax < min) { return false; } max = newMax; return true; }一個線程調用setA(8),一個線程調用setB(2),可能會得到min=8、max=2,這與最初代碼設計意圖不符合。
(二)原理分析
volatile實現記憶體可見度原理:
volatile Singleton instance = new Singleton();
instance變數在進行寫操作的時候,彙編下增加了lock指令。
lock指令的作用:
1.線程工作記憶體中的資料在進行寫操作的時候,會立即回寫到主記憶體中
2.回寫到主記憶體時,讓其他CPU中該資料的緩衝失效,下次讀取需要重新從主記憶體中擷取
/** * Volatile的使用測試 * * @author peter_wang * @create-time 2015-1-14 下午10:11:57 */public class ThreadVolatileDemo { private boolean isStop = false; private void changeStatus() { isStop = !isStop; } /** * @param args */ public static void main(String[] args) { final ThreadVolatileDemo test = new ThreadVolatileDemo(); Thread thread1 = new Thread() { @Override public void run() { System.out.println("嘗試退出開始"); while (true) { //一直檢測isStop變數是否更新 if (test.isStop) {//A1 System.out.println("退出成功"); System.exit(0); } } } }; thread1.start(); try { Thread.sleep(3000); test.changeStatus(); } catch (InterruptedException e) { e.printStackTrace(); } }}A1部分不斷迴圈重複讀取isStop值,但是讀取的是線程工作記憶體中的資料,而且不斷命中該資料,資料區塊不易被替換,增加讀取到舊值的機率。修改isStop變數為volatile,變數被修改後會立即回寫到主記憶體,A1讀取線程工作記憶體失效,從主記憶體中讀取到最新值。