volatile內存解析

閱讀本文需要熟悉java內存模型:Java內存模型-JMM解析

1.前言

java內存模型規定了所有的變量都存儲主內存,注意這裏的主內存,是依附在物理上的內存,jvm就運行在物理內存上(我的電腦是16G),所以這裏的主內存就是虛擬機的一部分,除此之外,每條線程還有自己的工作內存(本地內存),工作內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其他的硬件和編譯器優化線程的,工作內存中保存了被該線程使用的在主內存中的變量副本,線程對變量的讀取賦值等操作都必須在工作內存中進行,不能直接在主內存中進行讀寫

2.volatile的出現及特性

無論是concurrent包下的原子性操作還是synchronized同步機制都是確保共享變量(主內存裏的變量) 在多線程條件下共享變能夠在線程間交互,通信,保障其安全性,都是基於內存層面進行操作,而volatile同樣是Java虛擬機提供的一種輕量級的同步機制,下面說說它的特徵

  • 可見性:使它修飾的變量在線程間可見,可見指的是當一個線程修改了這個變量的值,新值對其他線程來說是可以立刻得知
  • 不保證原子性:對於類似++的自增操作,volatile不能確保其在多線程條件下的安全性,因此用原子類代替是一個可選的方案

3.volatile讀寫與happens-before的關係

簡述一下happens-before的特點(詳解請看開頭文章鏈接):

  • 如果一個操作與另一個操作有Happens-Before關係,那麼第一個操作將對第二個操作可見,且第一個操作的順序要在第二個操作之前

在java虛擬機規定的幾種happens-before原則中,volatile屬於其中一種,我們看一下它的內存定義:

  • 對volatile變量寫時的內存定義:當對一個volatile變量進行寫操作時,JMM會把該線程內的工作內存中的共享變量刷新到主內存
  • 對volatile變量讀時的內存定義:當對一個volatile變量進行讀操作時,JMM會把本線程內工作內存中的共享變量置爲無效,從主內存去讀取共享變量

這也是爲什麼volatile具有可見性的原因

4.volatile讀寫內存定義實現,避免指令重排

在此之前需要了解一下指令重排序,文章頭部有鏈接,volatile實現可見性還有一個重要原因:
避免指令重排
在這裏插入圖片描述
從表格可以看出:

  • 當第一個操作爲普通變量的讀寫時,如果第二個操作時時volatile寫,則不能把這兩個指令重排序
  • 當第二個操作爲volatile寫,無論第一個操作是什麼類型,都不能進行重排序
  • 當第一個操作爲volatile讀時,無論第二個操作是什麼類型,都不能進行重排序
  • 當第一個操作爲volatile寫第二個操作爲volatile讀時,不能進行重排序

這個表格就是對volatile關鍵字的讀寫定義,即在這種機制下,保證了共享變量的可見性和安全性,爲了實現這個定義,java編譯器在實現代碼時會在代碼中加入內存屏障來禁止處理器(cpu)的指令重排,具體實現如下:

  • 在每個volatile讀後插入LoadLoad屏障
  • 在每個volatile讀後插入LoadStore屏障
  • 在每個volatile寫前插入StoreStore屏障
  • 在每個volatile寫後插入StoreLoad屏障

我們通過一個例子來說明:

public class Demo {
    int a;
    volatile int v1 = 1;
    volatile int v2 = 2;
    void readAndWrite(){
        int i = v1;//第一個volatile讀
        int j = v2;//第二個volatile讀
        a = i + j;//普通寫
        v1 = i + 1;//第一個volatile寫
        v2 = j * 2;//第一個volatile寫
    }
}

針對上面的代碼,編譯器在生成字節碼時可以做到如下優化
在這裏插入圖片描述
通過這種方式就可以保障volatile的happens-before規則,很好的避免了指令重排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章