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讀取線程工作內存失效,從主內存中讀取到最新值。



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