java--volatile關鍵字

轉:https://www.cnblogs.com/selene/p/5972882.htmlhtml

volatile不能保證數據同步

volatile關鍵字比較少用,緣由無外乎兩點,一是在Java1.5以前該關鍵字在不一樣的操做系統上有不一樣的表現,所帶來的問題就是移植性較差;並且比較難設計,並且誤用較多,這也致使它的"名譽" 受損。安全

  咱們知道,每一個線程都運行在棧內存中,每一個線程都有本身的工做內存(Working Memory,好比寄存器Register、高速緩衝存儲器Cache等),線程的計算通常是經過工做內存進行交互的,其示意圖以下圖所示:多線程

  

  從示意圖上咱們能夠看到,線程在初始化時從主內存中加載所需的變量值到工做內存中,而後在線程運行時,若是是讀取,則直接從工做內存中讀取,如果寫入則先寫到工做內存中,以後刷新到主內存中,這是JVM的一個簡答的內存模型,可是這樣的結構在多線程的狀況下有可能會出現問題,好比:A線程修改變量的值,也刷新到了主內存,但B、C線程在此時間內讀取的仍是本線程的工做內存,也就是說它們讀取的不是最"新鮮"的值,此時就出現了不一樣線程持有的公共資源不一樣步的狀況。ide

  對於此類問題有不少解決辦法,好比使用synchronized同步代碼塊,或者使用Lock鎖來解決該問題,不過,Java可使用volatile更簡單地解決此類問題,好比在一個變量前加上volatile關鍵字,能夠確保每一個線程對本地變量的訪問和修改都是直接與內存交互的,而不是與本線程的工做內存交互的,保證每一個線程都能得到最"新鮮"的變量值,其示意圖以下:oop

  

  明白了volatile變量的原理,那咱們思考一下:volatile變量是否可以保證數據的同步性呢?兩個線程同時修改一個volatile是否會產生髒數據呢?咱們看看下面代碼:測試

class UnsafeThread implements Runnable {
    // 共享資源
    private volatile int count = 0;

    @Override
    public void run() {
        // 增長CPU的繁忙程度,沒必要關心其邏輯含義
        for (int i = 0; i < 1000; i++) {
            Math.hypot(Math.pow(92456789, i), Math.cos(i));
        }
        count++;
    }

    public int getCount() {
        return count;
    }
}

上面的代碼定義了一個多線程類,run方法的主要邏輯是共享資源count的自加運算,並且咱們還爲count變量加上了volatile關鍵字,確保是從內存中讀取和寫入的,若是有多個線程運行,也就是多個線程執行count變量的自加操做,count變量會產生髒數據嗎?想一想看,咱們已經爲count加上了volatile關鍵字呀!模擬多線程的代碼以下:spa

public static void main(String[] args) throws InterruptedException {
        // 理想值,並做爲最大循環次數
        int value = 1000;
        // 循環次數,防止形成無限循環或者死循環
        int loops = 0;
        // 主線程組,用於估計活動線程數
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        while (loops++ < value) {
            // 共享資源清零
            UnsafeThread ut = new UnsafeThread();
            for (int i = 0; i < value; i++) {
                new Thread(ut).start();
            }
            // 先等15毫秒,等待活動線程爲1
            do {
                Thread.sleep(15);
            } while (tg.activeCount() != 1);
            // 檢查實際值與理論值是否一致
            if (ut.getCount() != value) {
                // 出現線程不安全的狀況
                System.out.println("循環到:" + loops + " 遍,出現線程不安全的狀況");
                System.out.println("此時,count= " + ut.getCount());
                System.exit(0);
            }
        }

    }

想讓volatite變量"出點醜",仍是須要花點功夫的。此段程序的運行邏輯以下:操作系統

  • 啓動100個線程,修改共享資源count的值
  • 暫停15秒,觀察活動線程數是否爲1(即只剩下主線程再運行),若不爲1,則再等待15秒。
  • 判斷共享資源是不是不安全的,即實際值與理想值是否相同,若不相同,則發現目標,此時count的值爲髒數據。
  • 若是沒有找到,繼續循環,直到達到最大循環爲止。

運行結果以下:線程

    循環到:40 遍,出現線程不安全的狀況
    此時,count= 999
  這只是一種可能的結果,每次執行都有可能產生不一樣的結果。這也說明咱們的count變量沒有實現數據同步,在多個線程修改的狀況下,count的實際值與理論值產生了誤差,直接說明了volatile關鍵字並不能保證線程的安全。
  在解釋緣由以前,咱們先說一下自加操做。count++表示的是先取出count的值而後再加1,也就是count=count+1,因此,在某個緊鄰時間片斷內會發生以下神奇的事情:設計

(1)、第一個時間片斷

  A線程得到執行機會,由於有關鍵字volatile修飾,因此它從主內存中得到count的最新值爲998,接下來的事情又分爲兩種類型:

  • 若是是單CPU,此時調度器暫停A線程執行,讓出執行機會給B線程,因而B線程也得到了count的最新值998.
  • 若是是多CPU,此時線程A繼續執行,而線程B也同時得到了count的最新值998.

(2)、第二個片斷

  • 若是是單CPU,B線程執行完+1操做(這是一個原子處理),count的值爲999,因爲是volatile類型的變量,因此直接寫入主內存,而後A線程繼續執行,計算的結果也是999,從新寫入主內存中。
  • 若是是多CPU,A線程執行完加1動做後修改主內存的變量count爲999,線程B執行完畢後也修改主內存中的變量爲999

這兩個時間片斷執行完畢後,本來指望的結果爲1000,單運行後的值爲999,這表示出現了線程不安全的狀況。這也是咱們要說明的:volatile關鍵字並不能保證線程安全,它只能保證當前線程須要該變量的值時可以得到最新的值,而不能保證線程修改的安全性。

順便說一下,在上面的代碼中,UnsafeThread類的消耗CPU計算是必須的,其目的是加劇線程的負荷,以便出現單個線程搶佔整個CPU資源的情景,不然很難模擬出volatile線程不安全的狀況,你們能夠自行模擬測試。

相關文章
相關標籤/搜索