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;
}
}