Volatile總結

1. volatile作用

  • 在Java的內(nèi)存模型下,線程可以把變量保存在本地內(nèi)存中, 而不是直接在主存中進行讀寫,這就可能造成一個線程在主存中修改變量的值,但另外一個線程還繼續(xù)使用它自己的本地內(nèi)存中變量的值,造成數(shù)據(jù)不一致。要解決這個問題,可以把變量聲明為volatile,這就指示JVM當每次使用該變量時都得到主存中進行讀取,volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內(nèi)存中重讀該變量的值,而且,當成員變量值發(fā)生變化時,強迫線程將變化值寫回到共享內(nèi)存, 從而保證在任何時刻,不同的線程總是看到該成員變量的同一個值

在對volatile變量進行寫操作之前的所有可見的共享變量的寫操作,在另一個線程B讀這個volatile變量后,都會馬上對另一個線程B可見

  • 為了獲得更好的性能,JVM可能會對指令進行重排序,使用volitile會禁止重排序,不過這也在一定程度上降低了代碼執(zhí)行效率

2. volatile的正確使用

要使volatile變量提供理想的線程安全,必須同時滿足下面兩個條件:

  • 對變量的寫操作不依賴當前值
  • 該變量沒有包含在具有其他變量的不變式中

第一個條件使volatile變量不能用作線程安全計數(shù)器, i++看上去是一個單獨的操作,實際上是由讀取-修改-寫入三個操作序列組成的組合操作,必須以原子方式執(zhí)行,但volatile并不提供必須的原子性(如果是單線程操作,則可以忽略第一個條件)

假設現(xiàn)在有一個不變式:min < max


    private int min, max;

    public void setMin(int value) {
        if (value > max) {
            throw new IllegalArgumentException();
        }   
        min = value;
    }

    public void setMax(int max) {
        if (value < min) {
            throw new IllegalArgumentException();
        }   
        max = value;
    }   

如果將min和max都聲明為volatile并不足以保證線程安全,假設同一時間,線程A調(diào)用setMin(5), 線程B調(diào)用setMax(3), 由于volitile并不阻塞線程,則兩個操作都會正常進行,但最后min = 5, max = 3,這就會不符合條件

3. 正確使用volatile的模式

1. 狀態(tài)標志

```
volatile boolean shudownRequested;
public void shutdown() {
    shutdownRequested = true;
}

public void doWork() {
    while (!shutdownRequested) {
        ...
    }
}
```

這種類型的狀態(tài)標記的一個公共特性是:通常只有一種狀態(tài)轉(zhuǎn)換,如從false變?yōu)閠rue,這種模式可以擴展到來回轉(zhuǎn)換的狀態(tài)標志,但是只有在轉(zhuǎn)換周期不被察覺的情況下才能擴展(false->true, true->false), 此外,還需要某些原子狀態(tài)轉(zhuǎn)換機制,如原子變量

2. 一次性安全發(fā)布

```

public class BackgroundFloobleLoader {
    public volatile Flooble theFlooble;

    public void initInBackground() {
        // do lots of stuff
        theFlooble = new Flooble();  // this is the only write to theFlooble
    }   
}

public class SomeOtherClass {
    public void doWork() {
        while (true) { 
            // do some stuff...
            // use the Flooble, but only if it is ready
            if (floobleLoader.theFlooble != null) 
                doSomething(floobleLoader.theFlooble);
            }
        }
}
```

如果 theFlooble 引用不是 volatile 類型,doWork() 中的代碼在解除對 theFlooble 的引用時,將會得到一個不完全構造的 Flooble

該模式的一個必要條件是:被發(fā)布的對象必須是線程安全的,或者是有效的不可變對象(有效不可變意味著對象的狀態(tài)在發(fā)布之后永遠不會被修改)。volatile 類型的引用可以確保對象的發(fā)布形式的可見性,但是如果對象的狀態(tài)在發(fā)布后將發(fā)生更改,那么就需要額外的同步

3. 獨立觀察

設有一種環(huán)境傳感器能夠感覺環(huán)境溫度。一個后臺線程可能會每隔幾秒讀取一次該傳感器,并更新包含當前文檔的 volatile 變量。然后,其他線程可以讀取這個變量,從而隨時能夠看到最新的溫度值;使用該模式的另一種應用程序就是收集程序的統(tǒng)計信息

4. volatile bean模式

volatile bean模式中, JavaBean的所有數(shù)據(jù)成員都是volatile類型, 并且gettersetter方法除了獲取或設置屬性外,不能包含任何邏輯, 此外,對于對象引用的成員, 引用的對象必須是有效不可變的(這將禁止具有數(shù)組值得屬性, 因為當數(shù)組引用被聲明為 volatile 時,只有引用而不是數(shù)組本身具有 volatile語義)

5. 開銷較低的讀-寫鎖策略

如果讀操作遠遠超過寫操作,可以結合使用內(nèi)部鎖和 volatile 變量來減少公共代碼路徑的開銷

    public class CheesyCounter {
    
        private volatile int value;

        public int getValue() { return value; }

        public synchronized int increment() {
            return value++;
        }
    }

寫操作違反了使用 volatile 的第一個條件,因此不能使用 volatile 安全地實現(xiàn)計數(shù)器 —— 必須使用鎖,其中,鎖一次只允許一個線程訪問值,volatile 允許多個線程執(zhí)行讀操作,因此當使用 volatile 保證讀代碼路徑時,要比使用鎖執(zhí)行全部代碼路徑獲得更高的共享度 —— 就像讀-寫操作一樣。然而,要隨時牢記這種模式的弱點:如果超越了該模式的最基本應用,結合這兩個競爭的同步機制將變得非常困難

參考

  1. Volatile 關鍵字(上)
  2. 正確使用 Volatile 變量
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,788評論 11 349
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,894評論 0 11
  • 我沒有想到為什么自己會這樣的難以琢磨。想將一件事做的好一點,卻總是做的不好。在與同事的交談中,我的敏感神經(jīng)似乎被調(diào)...
    鹿鹿無畏閱讀 816評論 0 51
  • 關注公眾號:“程序員成長軟技能” ,日拱一卒,功不唐捐! 一、工具準備 kindle for pc (筆者 k...
    數(shù)大招瘋閱讀 14,946評論 1 4
  • 離殤 文/靈璧曙光 輕輕閉上眼睛 嘗試一下沒有你的情景 思念卻泛上心頭 腦海里來來往往的鏡像 滿滿的都是你的倩影 ...
    靈璧曙光閱讀 173評論 0 3

友情鏈接更多精彩內(nèi)容