Java多執行緒/併發09、淺談volatile

唐大麥發表於2017-04-28

首先宣告:現在JVM經過優化,已不會出現liveness failure 。所以沒事別用volatile。

在瞭解volatile之前,先介紹一個名詞:liveness failure ,直譯叫作活性失敗。因為這是volatile很重要的一個應用場景:

public class volatileDemo {
    private static boolean stopFlag;
    public static void main(String[] args) throws InterruptedException {
        Thread volatileThread =new Thread(){
            @Override
            public void run() {
                while(!stopFlag){
                    System.out.print(Calendar.getInstance().get(Calendar.SECOND)+",");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        volatileThread.start();
        Thread.sleep(3000);
        stopFlag=true;
    }
}

執行幾秒鐘之後,發現並沒有終止輸出。(jre1.7之前會出現)
主執行緒修改了變數stopFlag,子執行緒B卻沒有感知,稱為活性失敗。

Java記憶體模型(JMM)規定了所有的變數都儲存在主記憶體中,主記憶體中的變數為共享變數,而每條執行緒都有自己的工作記憶體,執行緒的工作記憶體儲存了從主記憶體拷貝的變數,所有對變數的操作都在自己的工作記憶體中進行,完成後再重新整理到主記憶體中。
這裡寫圖片描述

程式碼stopFlag=true;主執行緒(執行緒main)雖然對stopFlag的變數進行了修改且重新整理回主記憶體中(《深入理解java虛擬機器》中關於主記憶體與工作記憶體的互動協議提到變數在工作記憶體中改變後必須將該變化同步回主記憶體),但volatileThread執行緒讀的仍是自己工作記憶體的舊值導致出現多執行緒的可見性問題,解決辦法就是給stopFlag變數加上volatile關鍵字。

據effective Java中描述,這個問題涉及到JVM對while(!flag)這種形式有一個提升的優化,即:

while(!flag){}

進行提升優化:

if(flag){
    while(true){}
}

重點參考文章:
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

總結如下

  1. volatile重要工作是避免執行緒髒讀:當執行緒對volatile變數進行讀操作時,會先將自己工作記憶體中的變數置為無效,之後再通過主記憶體拷貝新值到工作記憶體中使用。
  2. volatile解決的是變數在多個執行緒之間的可見性,但不能完全保證資料的原子性。
  3. 現在JVM經過優化,已不會出現liveness failure 。所以沒事別用volatile。

相關文章