volatile 關鍵字

volatile 的底層實現原理是內存屏障,Memory Barrier(Memory Fence)ide

  • 對 volatile 變量的寫指令後會加入寫屏障
  • 對 volatile 變量的讀指令前會加入讀屏障

對一個 volatile 變量的單個讀/寫操做,與對一個普通變量的讀/寫操做使用同一個鎖來同步,它們之間的執行效果相同;也就是說對一個 volatile 變量的讀,老是能看到(任意線程) 對這個 volatile 變量最後的寫入。線程

值得注意的是:Java 內存模型對 volatile 語義的擴展保證了 volatile 變量在一些狀況下不會重排序,volatile 的 64 位變量 double 和 long 的讀取和賦值操做都是原子的。若是是多個 volatile 操做或相似於 volatile++ 這種複合操做,這些操做總體上不具備原子性。blog

簡而言之,volatile 變量自身具備一下特性:排序

  • 可見性:對一個 volatile 變量的讀,老是能看到(任意線程) 對這個 volatile 變量最後的寫入。
  • 原子性:對任意單個 volatile 變量的讀/寫具備原子性,可是相似於 volatile++ 這種符合操做不具備原子性。

volatile 寫-讀的內存語義

當寫一個 volatile 變量時,JMM 會把該線程棧中的共享變量值刷新到主內存,使主內存和該線程棧中的共享變量的值是一致的。內存

當讀一個 volatile 變量時,JMM 會把該線程棧中的共享變量的值置爲無效,隨後線程會從主內存中從新讀取共享變量。編譯器

下面對 volatile 寫和 volatile 讀的內存語義作個總結:同步

  • 線程 A 寫一個 volatile 變量,實質上是線程 A 向接下來將要讀這個 volatile 變量的某個線程發出了(這個變量已經被修改的)信息。
  • 線程 B 讀一個 volatile 變量,實質上是線程 B 接收了以前某個線程發出的(在寫這個 volatile 變量以前對共享變量所作修改的)信息。
  • 線程 A 寫一個 volatile 變量,隨後線程 B 讀這個 volatile 變量,這個過程實質上是線程 A 經過主內存想線程 B 發送消息。

簡而言之:線程 A 修改了 volatile 變量,經過主內存發送消息到下一個要讀 volatile 變量的線程;而後讀的這個線程會將線程棧中的這個 volatile 變量置爲無效,而後從新從主內存中讀取。it

volatile 內存語義的實現

volatile 實現 JVM 必須遵循如下重排序規則:編譯

從上表咱們能夠看出:class

  • 當第二個操做是 volatile 寫時,無論第一個操做是什麼,都不能重排序。這個規則確保 volatile 寫以前的操做不會被編譯器重排序到 volatile 寫以後。
  • 當第一個操做是 volatile 讀時,無論第二個操做是什麼,都不能重排序。這個規則確保 volatile 讀以後的操做不會被編譯器重排序到 volatile 都以前。
  • 當第一個操做是 volatile 寫,第二個操做是 volatile 讀時,不能重排序。

留白的單元格表明容許在不違反Java基本語義的狀況下重排序。例如,編譯器不會對對同一內存地址的讀和寫操做重排序,可是容許對不一樣地址的讀和寫操做重排序。

爲了實現 volatile 的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止聽頂類型的處理器重排序。下面是基於保守策略的 JMM 內存屏障插入策略:

  • 在每一個 volatile 寫操做的前面插入一個 StoreStore 屏障。
  • 在每一個 volatile 寫操做的後面插入一個 StoreLoad 屏障。
  • 在每一個 volatile 讀操做的後面插入一個 LoadLoad 屏障。
  • 在每一個 volatile 讀操做的後面插入一個 LoadStore 屏障。

下面是保守策略下,volatile 寫插入內存屏障後生成的指令序列示意圖:

上圖中的 StoreStore 屏障能夠保證在 volatile 寫以前,其前面的全部普通寫操做已經對任意處理器可見了。這是由於 StoreStore 屏障將保障上面全部的普通寫在 volatile 寫以前刷新到主內存。

StoreLoad 屏障避免 volatile 寫與後面可能有的 volatile 讀/寫操做重排序。由於編譯器經常沒法準確判斷在一個 volatile 寫的後面,是否須要插入一個 StoreLoad 屏障(好比,一個 volatile 寫以後方法當即 return)。爲了保證能正確實現 volatile 的內存語義,JMM 在這裏採起了保守策略:在每一個 volatile 寫的後面或在每一個 volatile 讀的前面插入一個 StoreLoad 屏障。從總體執行效率的角度考慮,JMM 選擇了在每一個 volatile 寫的後面插入一個 StoreLoad 屏障。

下面是在保守策略下,volatile 讀插入內存屏障後發生的指令序列示意圖:

上圖的 LoadLoad 屏障用來禁止處理器把上面的 volatile 讀與下面的普通讀重排序。LoadStore 屏障用來禁止處理器吧上面的 volatile 讀與下面的普通寫重排序。

保證可見性

保證有序性

保證

相關文章
相關標籤/搜索