一句話說一下volatile

原文:一句話說一下volatilejava

各位看官,這是個人開場白:volatile並不能保證變量是線程安全的,只能保證線程可以獲的取變量最新值。面試

爲何你們老是說 volatile 是輕量級的 synchronized ?輕量級由於它擁有較少的編碼和運行開銷,也不會形成阻塞,但代價是並不擁有synchronized的所有能力。編程

名詞解釋

  • 共享變量

若是一個變量在多個線程的工做內存中存在副本,那麼這個變量叫作共享變量。安全

  • 可見性

若是一個線程對共享變量的修改,能及時的被其餘線程看到,叫作共享變量的可見性。微信

  • 原子性

不可中斷的一個或一系列操做。架構

Java內存模型

咱們先來看看一張圖 編碼

主內存工做內存.jpg

  1. Java內存模型規定了全部變量都存儲在主內存,每一個線程還有本身的工做內存,線程的工做內存保存了被該線程使用到的變量的主內存的副本拷貝。spa

  2. 線程對變量的全部讀寫操做都必須在工做內存中進行,而不是直接讀寫主內存的變量,不一樣線程間也不能相互訪問對方的工做內存。線程

  3. 當線程對本身工做內存中的變量操做後,以後會刷新到主內存中,而後其餘線程再次讀取主內存中的變量才能獲取到最新值。code

因此這樣就形成了一些線程讀取的變量不是最新的!那麼volatile是怎麼解決這個問題的呢?

加關鍵字後.jpg

加入volatile關鍵字修飾變量後,線程對變量的操做能立馬反饋的主內存中,其餘線程嗅探到變化,使工做內存變量無效,使用時再次從主存中讀取,這樣對於其餘線程讀取的都是最新的值。

舉例

可見性

public class Test {

    public boolean flag = false;

    public void waiting() {
        System.out.println("等待flag改變……");
        while (!flag) {
        }
        System.out.println("flag改變完畢……" + flag);
    }
    
    public void change() {
        System.out.println("改變flag");
        flag = true;
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        new Thread(() -> test.waiting()).start();
        Thread.sleep(3000L);
        new Thread(() -> test.change()).start();
    }
}
複製代碼

執行上面代碼,你會發現第一個線程一直處於等待中, 這就符合了上面內存模型的解釋了。

而後把變量flag加上關鍵字volatile,再次運行,你會發現改變flag後,線程就馬上運行結束了。

線程不安全狀況

public class Test {

    public volatile int count = 0;

    public void change() {
        for (int i = 0; i < 100000; i++) {
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        new Thread(() -> test.change()).start();
        new Thread(() -> test.change()).start();
        new Thread(() -> test.change()).start();
        Thread.sleep(2000L);
        System.out.println(test.count);
    }
}
複製代碼

上面代碼中變量count加上了volatile關鍵字,咱們指望三個線程執行後,得出count的值爲30萬。但實際得出的卻遠遠不足30萬。

由於volatile對變量自己的單次讀寫具備原子性,但count++並非原子操做,它實際上有三步(讀取-修改-寫入),當多個線程同時進入這三步中,就會出現問題。

可是它也有線程安全的場景,接下來聊聊。

使用條件

volatile 不能代替 synchronized , 但在有限的條件下能夠用於線程安全,必須同時知足下面兩條件:

  1. 對變量的寫不能依賴於當前變量的值。(反例:count = count + 1
  2. 當前 volatile 變量不包含在其餘變量的不變式中。(反例:volatile1 < var2恆成立)

大多數編程場景都會與這兩個條件之一衝突,這也就造就了 volatile 的使用不像 synchronized 那麼廣泛。

平時使用的場景

狀態標記量

這個咱們已經在可見性中寫出了。這類有個共性就是:狀態標記量不依賴於程序內其餘狀態,一般只有一種狀態轉換。

雙重檢測

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;
    }
}
複製代碼

這類結合了 synchronized 實現了 volatile 變量的惟一賦值,當賦值成功後其餘線程在獲取鎖以前即可當即知道變量的變化。

往期文章一覽

千萬不要這樣使用 Arrays.asList !

八張圖帶你認識Java

Java開發必備手冊

關注微信公衆號 「碼上實戰」 回覆 :面試視頻 和 架構師 送你很是不錯的資料!

相關文章
相關標籤/搜索