天天看點

Java Volatile 的應用場景

4.4 volatile的應用場景

4.4.1 正确使用volatile條件

條件一: 寫入變量時并不依賴變量的目前值;或者能夠確定隻有單一線程能夠修改變量的值

條件二: 變量不需要與其他的狀态變量共同參與不變限制

條件三: 變量通路不需要額外加鎖

通俗點: 當一個變量依賴其他變量或變量的新值依賴舊值時,不能用volatile

4.4.2 volatile使用場景

适用場合:多個線程讀,一個線程寫的場合

使用場景:通常被 作為辨別完成、中斷、狀态的标記,值變化應具有原子性

充分利用其可見性:即volatile能夠保證在讀取的那個時刻讀到的肯定是最新值

重點聲明: volatile主要使用的場合是在多線程中可以感覺執行個體變量被變更了,并且可以獲得最新的值使用,也就是用多線程讀取共享變量時可以獲得最新值使用,但不能保證你在使用最新值過程中最新值不發生變化!很可能在使用之後,最新值已經變更。原資料變成過期資料,這時候就會出現資料不一緻(非同步)的問題

4.4.3 正确使用volatile

狀态标志

//使用:作為一個布爾狀态标志,用于訓示發生了一個重要的一次性事件,例如完成初始化或任務結束

//使用理由:狀态标志并不依賴于程式内任何其他狀态,且通常隻有一種狀态轉換

//例子:判斷業務是否結束
volatile boolean isOk = false;
public void isOk() { isOk = true; }
 public void doWork() { 
    //循環監聽狀态位變化
    while (!isOk) { 
        // do work
    }
}      

獨立觀察

//使用:将 volatile變量用于多個獨立觀察結果的釋出

//特點:是"狀态标志"的拓展,該值随時會發生變化,同時會被反複使用,前者一般就是用一次

//使用理由:隻是簡單的指派操作,不會做複合操作
//例子:将新節點作為最後一個節點
class CustomLinkedList{
    public volatile Node lastNode;
    .....
    public void add() {
        Node node = new Node();
        .....
        lastNode = node;//将新節點作為最後一個節點
    }
}      

開銷較低的讀-寫鎖政策

//使用:當讀遠多于寫,結合使用内部鎖和 volatile 變量來減少同步的開銷

//使用理由:利用volatile保證讀取操作的可見性;利用synchronized保證複合操作的原子性

@ThreadSafe
public class Counter {
    private volatile int value;
    public int getValue() { return value; }//利用volatile保證讀取操作的可見性
    public synchronized int increment() { //利用synchronized保證複合操作的原子性
        return value++;
    }
}      

一次性安全釋出

//雙重檢查鎖定:實作線程安全的延遲初始化,同時降低同步開銷

//    1.多線程并發建立對象時,會通過加鎖保證隻有一個線程能建立對象
//    2.對象建立完畢,執行get方法将不需要擷取鎖,直接傳回建立對象
//隐患:多線程環境下,由于重排序,該對象可能還完成初始化就被其他線程讀取
//
public class DoubleSynchronizedSingleton {
    private static DoubleSynchronizedSingleton doubleSynchronizedSingleton;
    private DoubleSynchronizedSingleton(){
    }
    //雙重鎖設計
    public static DoubleSynchronizedSingleton getInstance(){
        if (doubleSynchronizedSingleton == null){
        //1.多線程并發建立對象時,會通過加鎖保證隻有一個線程能建立對象
            synchronized (DoubleSynchronizedSingleton.class){
                if (doubleSynchronizedSingleton == null){
                    //隐患:多線程環境下,由于重排序,該對象可能還完成初始化就被其他線程讀取
                    doubleSynchronizedSingleton = new DoubleSynchronizedSingleton();//問題代碼
                }
            }
        }
        //2.對象建立完畢,執行get方法将不需要擷取鎖,直接傳回建立對象
        return doubleSynchronizedSingleton;
    }
}      

單線程環境下(或者說正常情況下),在"問題代碼處",會執行如下操作,保證能擷取到已完成初始化的執行個體

image_1bnnou6sdvnmb4g1rprg0a10gt1s.png-16.8kB

隐患:多線程環境下,在"問題代碼處",會執行如下操作,由于重排序導緻2,3亂序,後果就是其他線程得到的是null而不是完成初始化的對象

image_1bnnougikstm13ctbdpojk4jc29.png-21.8kB

//基于volatile的解決方案
public class SafeDoubleCheckSingleton {
    //通過volatile聲明,實作線程安全的延遲初始化
    private volatile static SafeDoubleCheckSingleton singleton;
    private SafeDoubleCheckSingleton(){
    }
    public static SafeDoubleCheckSingleton getInstance(){
        if (singleton == null){
            synchronized (SafeDoubleCheckSingleton.class){
                if (singleton == null){
                    //原理利用volatile在于 禁止 "初始化對象"(2) 和 "設定singleton指向記憶體空間"(3) 的重排序
                    singleton = new SafeDoubleCheckSingleton();
                }
            }
        }
        return singleton;
    }
}      

參考