volatile 的底層實現原理是內存屏障,Memory Barrier(Memory Fence)ide
對一個 volatile 變量的單個讀/寫操做,與對一個普通變量的讀/寫操做使用同一個鎖來同步,它們之間的執行效果相同;也就是說對一個 volatile 變量的讀,老是能看到(任意線程) 對這個 volatile 變量最後的寫入。線程
值得注意的是:Java 內存模型對 volatile 語義的擴展保證了 volatile 變量在一些狀況下不會重排序,volatile 的 64 位變量 double 和 long 的讀取和賦值操做都是原子的。若是是多個 volatile 操做或相似於 volatile++ 這種複合操做,這些操做總體上不具備原子性。blog
簡而言之,volatile 變量自身具備一下特性:排序
當寫一個 volatile 變量時,JMM 會把該線程棧中的共享變量值刷新到主內存,使主內存和該線程棧中的共享變量的值是一致的。內存
當讀一個 volatile 變量時,JMM 會把該線程棧中的共享變量的值置爲無效,隨後線程會從主內存中從新讀取共享變量。編譯器
下面對 volatile 寫和 volatile 讀的內存語義作個總結:同步
簡而言之:線程 A 修改了 volatile 變量,經過主內存發送消息到下一個要讀 volatile 變量的線程;而後讀的這個線程會將線程棧中的這個 volatile 變量置爲無效,而後從新從主內存中讀取。it
volatile 實現 JVM 必須遵循如下重排序規則:編譯
從上表咱們能夠看出:class
留白的單元格表明容許在不違反Java基本語義的狀況下重排序。例如,編譯器不會對對同一內存地址的讀和寫操做重排序,可是容許對不一樣地址的讀和寫操做重排序。
爲了實現 volatile 的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止聽頂類型的處理器重排序。下面是基於保守策略的 JMM 內存屏障插入策略:
下面是保守策略下,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 讀與下面的普通寫重排序。