1. 程式人生 > >Java並發(四):volatile的實現原理

Java並發(四):volatile的實現原理

ont style tile 讀寫 flush microsoft div 圖片 println

synchronized是一個重量級的鎖,volatile通常被比喻成輕量級的synchronized

volatile是一個變量修飾符,只能用來修飾變量。

volatile寫:當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。

volatile讀:當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量。

volatile實現原理

1)JMM把內存屏障指令分為下列四類:

技術分享圖片

StoreLoad Barriers是一個“全能型”的屏障,它同時具有其他三個屏障的效果。現代的多處理器大都支持該屏障(其他類型的屏障不一定被所有處理器支持)。執行該屏障開銷會很昂貴,因為當前處理器通常要把寫緩沖區中的數據全部刷新到內存中(buffer fully flush)。

Store:數據對其他處理器可見(即:刷新到內存)

Load:讓緩存中的數據失效,重新從主內存加載數據

2)JMM針對編譯器制定的volatile重排序規則表

是否能重排序 第二個操作
第一個操作 普通讀/寫 volatile讀 volatile寫
普通讀/寫 NO
volatile讀 NO NO NO
volatile寫 NO NO

舉例來說,第三行最後一個單元格的意思是:在程序順序中,當第一個操作為普通變量的讀或寫時,如果第二個操作為volatile寫,則編譯器不能重排序這兩個操作。

從上表我們可以看出:

  • 當第二個操作是volatile寫時,不管第一個操作是什麽,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之後。
  • 當第一個操作是volatile讀時,不管第二個操作是什麽,都不能重排序。這個規則確保volatile讀之後的操作不會被編譯器重排序到volatile讀之前。
  • 當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排序。

JMM內存屏障插入策略(編譯器可以根據具體情況省略不必要的屏障):

  • 在每個volatile寫操作的前面插入一個StoreStore屏障。
    • 對於這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
  • 在每個volatile寫操作的後面插入一個StoreLoad屏障。
    • 對於這樣的語句Store1; StoreLoad
      ; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。
  • 在每個volatile讀操作的後面插入一個LoadLoad屏障。
    • 對於這樣的語句Load1; LoadLoad; Load2,在Load2及後續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
  • 在每個volatile讀操作的後面插入一個LoadStore屏障。
    • 對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。

volatile保證可見性

  volatile修飾的變量寫之後將本地內存刷新到主內存,保證了可見性

volatile保證有序性

  volatile變量讀寫前後插入內存屏障以避免重排序,保證了有序性

volatile不保證原子性

  volatile不是鎖,與原子性無關

  要我說,由於CPU按照時間片來進行線程調度的,只要是包含多個步驟的操作的執行,天然就是無法保證原子性的。因為這種線程執行,又不像數據庫一樣可以回滾。如果一個線程要執行的步驟有5步,執行完3步就失去了CPU了,失去後就可能再也不會被調度,這怎麽可能保證原子性呢。

為什麽synchronized可以保證原子性 ,因為被synchronized修飾的代碼片段,在進入之前加了鎖,只要他沒執行完,其他線程是無法獲得鎖執行這段代碼片段的,就可以保證他內部的代碼可以全部被執行。進而保證原子性。(摘自http://www.hollischuang.com/archives/2673)

volatile不保證原子性的例子:

/**
 * 創建10個線程,然後分別執行1000次i++操作。目的是程序輸出結果10000
 * 但是,多次執行的結果都小於10000。這其實就是volatile無法滿足原子性的原因。
 */
public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        test.increase();
                };
            }.start();
        }

        while (Thread.activeCount() > 1)
            // 保證前面的線程都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

參考資料

深入理解Java中的volatile關鍵字

Java並發(一):Java內存模型幹貨總結

【死磕Java並發】—–深入分析volatile的實現原理

再有人問你volatile是什麽,把這篇文章也發給他。

Java並發(四):volatile的實現原理