volatile的原理

volatile 的底層實(shí)現(xiàn)原理是內(nèi)存屏障饿序。Memory Barrier

  • 對(duì)volatile寫(xiě)指令后會(huì)加入寫(xiě)屏障
  • 對(duì)volatile讀指令前會(huì)加入讀屏障

如何保證可見(jiàn)性

寫(xiě)屏障保證在該屏障之前葡秒,對(duì)共享變量的改動(dòng)谒养,都同步到主存中去撇贺。

  int number = -1;
   
  volatile boolean ready =false;

   @Actor
   public void actor2(I_Result r){
       number =2;
       ready=true;  //ready是 volatile 有寫(xiě)屏障
                //寫(xiě)屏障
  }

讀屏障保證在該屏障之后,對(duì)共享變量的讀取育八,都是最新的數(shù)據(jù)。

   @Actor
   public void actor1(I_Result r){
       //讀屏障
                // ready是volatile斯够,帶讀屏障
       if(ready){
           r.r1 = number+number;
       }else{
           r.r1=1;
       }   
   }

image.png

如何保證有序性

寫(xiě)屏障會(huì)確保指令重排時(shí),不會(huì)將寫(xiě)屏障之前的代碼排在寫(xiě)屏障之后

   @Actor
   public void actor2(I_Result r){
       number =2;
       ready=true;  //ready是 volatile 有寫(xiě)屏障
                //寫(xiě)屏障
  }

讀屏障會(huì)確保指令重排是喧锦,不會(huì)將寫(xiě)屏障之后的代碼排在讀屏障之前

   @Actor
   public void actor1(I_Result r){
       //讀屏障
                // ready是volatile读规,帶讀屏障
       if(ready){
           r.r1 = number+number;
       }else{
           r.r1=1;
       }   
   }

volatile不能解決指令交錯(cuò)的問(wèn)題。它只是解決了可見(jiàn)性燃少,和有序性的問(wèn)題束亏。而有序性也只是解決了線(xiàn)程內(nèi)部不進(jìn)行指令重排。

image.png

volatile如何保證可見(jiàn)性

package com.conrrentcy.atomic;

public class VisibilityVolatile {
    public static void main(String[] args) throws InterruptedException {
        Monitor1 monitor = new Monitor1();

        monitor.startMonitor();

        Thread.sleep(1000);

        monitor.stop();
        
        
        Monitor2 monitor2 = new Monitor2();
        
        monitor2.startMonitor();
        
        Thread.sleep(1000);
        monitor2.stop();
    }
}

class Monitor1 {
    Thread monitor = null;
    private volatile boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void stop() {
        this.isRunning = false;
    }

    public void startMonitor() {
        monitor = new Thread(() -> {
            while (true) {

                if (!isRunning) {
                    break;
                }
            }

        });

        monitor.start();
    }

}

class Monitor2 {
    Thread monitor = null;
    private Object lock = new Object();

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void stop() {
        synchronized (lock) {
            this.isRunning = false;
        }
    }

    public void startMonitor() {
        monitor = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (!isRunning) {
                        break;
                    }

                }
            }

        });

        monitor.start();
    }
}

利用工具h(yuǎn)sdis供汛,打印出匯編指令枪汪,可以發(fā)現(xiàn)涌穆,加了volatile修飾之后打印出來(lái)的匯編指令多了下面一行:
0x000000010f030785: lock addl $0x0,(%rsp) ;*putfield queue

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
hsdis-amd64.dylib放在$JAVA_PATH/jre/lib/server/中

lock是一種控制指令怔昨,在多處理器環(huán)境下,lock 匯編指令可以基于總線(xiàn)鎖或者緩存鎖的機(jī)制來(lái)達(dá)到可見(jiàn)性的一個(gè)效果宿稀。

  • StoreStoreBarrier|volatile 寫(xiě)操作|StoreLoadBarrier(寫(xiě)寫(xiě)屏障和寫(xiě)讀屏障)
  • LoadLoadBarrier|volatile 讀操作|LoadStoreBarrier(讀讀屏障和讀寫(xiě)屏障)

CPU層面的內(nèi)存屏障

CPU內(nèi)存屏障主要分為以下三類(lèi):

  • 寫(xiě)屏障(Store Memory Barrier):告訴處理器在寫(xiě)屏障之前的所有已經(jīng)存儲(chǔ)在存儲(chǔ)緩存(store bufferes)中的數(shù)據(jù)同步到主內(nèi)存趁舀,簡(jiǎn)單來(lái)說(shuō)就是使得寫(xiě)屏障之前的指令的結(jié)果對(duì)寫(xiě)屏障之后的讀或者寫(xiě)是可見(jiàn)的。
  • 讀屏障(Load Memory Barrier):處理器在讀屏障之后的讀操作,都在讀屏障之后執(zhí)行祝沸。配合寫(xiě)屏障矮烹,使得寫(xiě)屏障之前的內(nèi)存更新對(duì)于讀屏障之后的讀操作是可見(jiàn)的。
  • 全屏障(Full Memory Barrier):確保屏障前的內(nèi)存讀寫(xiě)操作的結(jié)果提交到內(nèi)存之后罩锐,再執(zhí)行屏障后的讀寫(xiě)操作奉狈。

JVM層面

在JVM層面,定義了一種抽象的內(nèi)存模型(JMM)來(lái)規(guī)范并控制重排序涩惑,從而解決可見(jiàn)性問(wèn)題仁期。

JMM(Java內(nèi)存模型)

JMM全稱(chēng)是Java Memory Model(Java內(nèi)存模型),什么是JMM呢?通過(guò)前面的分析發(fā)現(xiàn)竭恬,導(dǎo)致可見(jiàn)性問(wèn)題的根本原因是緩存以及指令重排序跛蛋。 而JMM 實(shí)際上就是提供了合理的禁用緩存以及禁止重排序的方法。所以JMM最核心的價(jià)值在于解決可見(jiàn)性和有序性痊硕。
JMM屬于語(yǔ)言級(jí)別的抽象內(nèi)存模型赊级,可以簡(jiǎn)單理解為對(duì)硬件模型的抽象,它定義了共享內(nèi)存中多線(xiàn)程程序讀寫(xiě)操作的行為規(guī)范岔绸,通過(guò)這些規(guī)則來(lái)規(guī)范對(duì)內(nèi)存的讀寫(xiě)操作從而保證指令的正確性理逊,它解決了CPU 多級(jí)緩存橡伞、處理器優(yōu)化、指令重排序?qū)е碌膬?nèi)存訪(fǎng)問(wèn)問(wèn)題晋被,保證了并發(fā)場(chǎng)景下的可見(jiàn)性骑歹。
需要注意的是,JMM并沒(méi)有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來(lái)提升指令執(zhí)行速度墨微,也沒(méi)有限制編譯器對(duì)指令進(jìn)行重排序道媚,也就是說(shuō)在JMM中,也會(huì)存在緩存一致性問(wèn)題和指令重排序問(wèn)題翘县。只是JMM把底層的問(wèn)題抽象到JVM層面最域,再基于CPU層面提供的內(nèi)存屏障指令,以及限制編譯器的重排序來(lái)解決并發(fā)問(wèn)題锈麸。

JMM抽象模型結(jié)構(gòu)

JMM 抽象模型分為主內(nèi)存镀脂、工作內(nèi)存欺矫;主內(nèi)存是所有線(xiàn)程共享的舅锄,一般是實(shí)例對(duì)象、靜態(tài)字段舷丹、數(shù)組對(duì)象等存儲(chǔ)在堆內(nèi)存中的變量氓奈。工作內(nèi)存是每個(gè)線(xiàn)程獨(dú)占的翘魄,線(xiàn)程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,不能直接讀寫(xiě)主內(nèi)存中的變量舀奶,線(xiàn)程之間的共享變量值的傳遞都是基于主內(nèi)存來(lái)完成暑竟,可以抽象為下圖:

image.png

JMM如何解決可見(jiàn)性

image.png

從JMM的抽象模型結(jié)構(gòu)圖來(lái)看,如果線(xiàn)程A與線(xiàn)程B之間要通信的話(huà)育勺,必須要經(jīng)歷下面2個(gè)步驟但荤。
1)線(xiàn)程A把本地內(nèi)存A中更新過(guò)的共享變量刷新到主內(nèi)存中去。
2)線(xiàn)程B到主內(nèi)存中去讀取線(xiàn)程A之前已更新過(guò)的共享變量涧至。
下面通過(guò)示意圖來(lái)說(shuō)明這兩個(gè)步驟:

結(jié)合上圖腹躁,假設(shè)初始時(shí),這3個(gè)內(nèi)存中的x值都為0南蓬。線(xiàn)程A在執(zhí)行時(shí)纺非,把更新后的x值(假設(shè)值為1)臨時(shí)存放在自己的本地內(nèi)存 A中。當(dāng)線(xiàn)程A和線(xiàn)程B需要通信時(shí)蓖康,線(xiàn)程A首先會(huì)把自己本地內(nèi)存中修改后的x值刷新到主內(nèi) 存中铐炫,此時(shí)主內(nèi)存中的x值變?yōu)榱?。隨后蒜焊,線(xiàn)程B到主內(nèi)存中去讀取線(xiàn)程A更新后的x值倒信,此時(shí)線(xiàn)程B的本地內(nèi)存的x值也變?yōu)榱?。 從整體來(lái)看泳梆,這兩個(gè)步驟實(shí)質(zhì)上是線(xiàn)程A在向線(xiàn)程B發(fā)送消息鳖悠,而且這個(gè)通信過(guò)程必須要經(jīng)過(guò)主內(nèi)存榜掌。JMM通過(guò)控制主內(nèi)存與每個(gè)線(xiàn)程的本地內(nèi)存之間的交互,來(lái)為Java程序員提供內(nèi)存可見(jiàn)性保證乘综。

JMM層面的內(nèi)存屏障

在JMM 中把內(nèi)存屏障分為四類(lèi):

image.png

持該屏障(其他類(lèi)型的屏障不一定被所有處理器支持)憎账。執(zhí)行該屏障開(kāi)銷(xiāo)會(huì)很昂貴,因?yàn)楫?dāng)前處理器通常要把寫(xiě)緩沖區(qū)中的數(shù)據(jù)全部刷新到內(nèi)存中(Buffer Fully Flush)卡辰。
JMM Happen-Before 原則

happens-before的概念最初由Leslie Lamport在其一篇影響深遠(yuǎn)的論文(《Time胞皱,Clocks and the Ordering of Events in a Distributed System》)中提出。
《JSR-133:Java Memory Model and Thread Specification》對(duì)happens-before關(guān)系的定義如下:
(1)程序順序規(guī)則:一個(gè)線(xiàn)程中的每個(gè)操作九妈,happens-before于該線(xiàn)程中的任意后續(xù)操作反砌。
(2)監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖萌朱。
(3)volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě)宴树,happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀。
(4)傳遞性:如果A happens-before B晶疼,且B happens-before C酒贬,那么A happens-before C。
(5)start()規(guī)則:如果線(xiàn)程A執(zhí)行操作ThreadB.start()(啟動(dòng)線(xiàn)程B)翠霍,那么A線(xiàn)程的ThreadB.start()操作happens-before于線(xiàn)程B中的任意操作锭吨。
(6)Join()規(guī)則:如果線(xiàn)程A執(zhí)行操作ThreadB.join()并成功返回,那么線(xiàn)程B中的任意操作happens-before于線(xiàn)程A從ThreadB.join()操作成功返回壶运。
(7)程序中斷規(guī)則:對(duì)線(xiàn)程interrupted()方法的調(diào)用先行于被中斷線(xiàn)程的代碼檢測(cè)到中斷時(shí)間的發(fā)生耐齐。
(8)對(duì)象finalize規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行于發(fā)生它的finalize()方法的開(kāi)始浪秘。

Lock 前綴指令有內(nèi)存屏障的作用蒋情。
一共有4種內(nèi)存屏障,分別是 LoadLoad耸携、LoadStore棵癣、StoreStore、StoreLoad夺衍。

  • LoadLoad:確保 Load1 數(shù)據(jù)的讀取先于 Load2 的數(shù)據(jù)及所有后續(xù)數(shù)據(jù)的讀取狈谊。
  • LStoreStore:確保 Store1 數(shù)據(jù)的寫(xiě)回先于 Store2 數(shù)據(jù)及所有后續(xù)數(shù)據(jù)的寫(xiě)回。
  • LLoadStore:確保 Load1 數(shù)據(jù)的讀取先于 Store2 數(shù)據(jù)及所有后續(xù)數(shù)據(jù)的寫(xiě)回沟沙。
  • LStoreLoad:確保 Store1 數(shù)據(jù)的寫(xiě)回先于 Load2 數(shù)據(jù)及所有后續(xù)數(shù)據(jù)的讀取河劝。
    JMM 插入內(nèi)存屏障保守策略:
    在每個(gè) volatile 寫(xiě)操作的前面插入一個(gè) StoreStore 屏障,后面插入一個(gè) StoreLoad 屏障矛紫。
    在每個(gè) volatile 讀操作的后面插入一個(gè) LoadLoad 屏障和一個(gè) LoadStore 屏障赎瞎。


    image.png

    由于 JMM插入內(nèi)存屏障保守策略增加了很多內(nèi)存屏障, 增加很多開(kāi)銷(xiāo)颊咬,性能會(huì)下降务甥,所以很多處理器對(duì)內(nèi)存屏障的插入進(jìn)行了優(yōu)化牡辽。如 X86/X64 處理器,只會(huì)在寫(xiě)操作后面增加一個(gè) storeLoad 內(nèi)存屏障敞临。


    image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末态辛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挺尿,更是在濱河造成了極大的恐慌奏黑,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件编矾,死亡現(xiàn)場(chǎng)離奇詭異攀涵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)洽沟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)以故,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人裆操,你說(shuō)我怎么就攤上這事怒详。” “怎么了踪区?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵昆烁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缎岗,道長(zhǎng)静尼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任传泊,我火速辦了婚禮鼠渺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘眷细。我一直安慰自己拦盹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布溪椎。 她就那樣靜靜地躺著普舆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪校读。 梳的紋絲不亂的頭發(fā)上沼侣,一...
    開(kāi)封第一講書(shū)人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音歉秫,去河邊找鬼蛾洛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛端考,可吹牛的內(nèi)容都是我干的雅潭。 我是一名探鬼主播揭厚,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扶供!你這毒婦竟也來(lái)了筛圆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤椿浓,失蹤者是張志新(化名)和其女友劉穎太援,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扳碍,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡提岔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笋敞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碱蒙。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖夯巷,靈堂內(nèi)的尸體忽然破棺而出赛惩,到底是詐尸還是另有隱情,我是刑警寧澤趁餐,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布喷兼,位于F島的核電站,受9級(jí)特大地震影響后雷,放射性物質(zhì)發(fā)生泄漏季惯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一臀突、第九天 我趴在偏房一處隱蔽的房頂上張望勉抓。 院中可真熱鬧,春花似錦惧辈、人聲如沸琳状。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至困食,卻和暖如春边翁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硕盹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工符匾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘩例。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓啊胶,卻偏偏與公主長(zhǎng)得像甸各,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子焰坪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • volatile 可以使得在多處理器環(huán)境下保證了共享變量的可見(jiàn)性趣倾,那么到底什么是可見(jiàn)性呢?在單線(xiàn)程的環(huán)境下某饰,如果向...
    WEIJAVA閱讀 309評(píng)論 0 1
  • 還記得上一篇文章當(dāng)中提到的內(nèi)存屏障(Memory Fence)嗎儒恋?其實(shí)Volatile的實(shí)現(xiàn)原理就是通過(guò)內(nèi)存屏障來(lái)...
    我犟不過(guò)你閱讀 280評(píng)論 0 2
  • 計(jì)算機(jī)內(nèi)存模型 計(jì)算機(jī)在執(zhí)行程序時(shí),每條指令都是在CPU中執(zhí)行的黔漂,而執(zhí)行指令過(guò)程中诫尽,勢(shì)必涉及到數(shù)據(jù)的讀取和寫(xiě)入。由...
    azmohan閱讀 338評(píng)論 0 1
  • 通過(guò)前面一章我們了解了synchronized是一個(gè)重量級(jí)的鎖炬守,雖然JVM對(duì)它做了很多優(yōu)化牧嫉,而下面介紹的volat...
    鐵甲依然在_978f閱讀 241評(píng)論 0 3
  • Synchronized原理:https://www.cnblogs.com/aspirant/p/1147085...
    闊闊飛翔閱讀 388評(píng)論 0 0