java并發(fā)之volatile

在Java并發(fā)編程中,volatile和synchronized都扮演者重要的角色。volatile又被成為輕量級(jí)的synchronized,它保證了共享變量的可見性饿自。

注:何謂可見性汰翠?
通俗點(diǎn)兒說,可見性就是當(dāng)一個(gè)線程修改一個(gè)共享變量時(shí)昭雌,其他的線程也可以讀到修改后的值复唤。

如果volatile使用得當(dāng),由于不會(huì)引起線程的上下文切換和調(diào)度城豁,它的開銷會(huì)比synchronized關(guān)鍵字小很多苟穆。本文將會(huì)深入分析volatile關(guān)鍵字實(shí)現(xiàn)原理,相信通過本文唱星,大家可以更好的使用volatile關(guān)鍵字雳旅。

如何保證可見性

在Java內(nèi)存模型(Java Memory Model,JMM)中间聊,線程和主存之間存在這樣的抽象關(guān)系:

  1. 線程之間的共享變量存放在主內(nèi)存(Main Memory)中攒盈;

  2. 每個(gè)線程都有一個(gè)私有的本地內(nèi)存(Local Memory),存放了當(dāng)前線程共享變量的副本哎榴。

那針對(duì)被volatile修飾的變量的讀/寫到底是什么樣子的呢型豁?

  1. 當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)把當(dāng)前線程的本地內(nèi)存中的共享變量值刷新到主內(nèi)存中尚蝌;

  2. 當(dāng)讀一個(gè)volatile變量時(shí)迎变,JMM會(huì)把當(dāng)前線程的本地內(nèi)存置為無效,然后從主內(nèi)存中讀取共享變量飘言。

對(duì)Java內(nèi)存模型有一些了解的一定對(duì)指令重排序不陌生衣形,一個(gè)程序在運(yùn)行中,編譯器和處理器為了優(yōu)化程序性能姿鸿,會(huì)對(duì)指令序列進(jìn)行重排序谆吴,那這時(shí)候就有問題了,那既然指令會(huì)被重新排序苛预,volatile是怎么保證內(nèi)存可見性呢句狼?萬一被重排了,內(nèi)存可見不就會(huì)有問題了~當(dāng)然咯热某,為了保證volatile的內(nèi)存可見性腻菇,編譯器在生成字節(jié)碼的時(shí)候會(huì)在指令序列中插入內(nèi)存屏障(Memory Barriers)來禁止特定類型的編譯器和處理器重排序。一旦內(nèi)存屏障被插入昔馋,就相當(dāng)于告訴了編譯器和處理器:不管是什么指令都不能和內(nèi)存屏障進(jìn)行重排序芜繁。

注:內(nèi)存屏障(Memory Barriers):一組CPU指令,用于實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制绒极。

我們還是以一個(gè)簡(jiǎn)單的例子來解釋下:


volatile測(cè)試demo

從test()方法可以看出:

  • 如果d不是volatile變量,1/2/3三個(gè)語句是可以進(jìn)行隨意進(jìn)行指令重排序的蔬捷;

注:為什么4不能和1/2/3一起指令重排序垄提?因?yàn)?和1/2/3存在數(shù)據(jù)依賴關(guān)系榔袋,編譯器和處理器在重排序時(shí)會(huì)遵守?cái)?shù)據(jù)依賴性,不會(huì)改變存在數(shù)據(jù)依賴關(guān)系的兩個(gè)操作的執(zhí)行順序铡俐。所以4不會(huì)參與到1/2/3的重排序行列凰兑。

  • d被volatile修飾后,會(huì)在1/2后插入一個(gè)內(nèi)存屏障审丘,此時(shí)吏够,1/2可以隨意進(jìn)行指令重排序,3不再參與到重排序行列滩报。

接下來我們?cè)趚86下通過工具生成的匯編指令來看看對(duì)volatile變量進(jìn)行讀寫操作時(shí)CPU會(huì)干什么锅知。

instance = new Singleton(); //instance是volatile變量

匯編指令:
lock addl $0x0, (%rsp)

從匯編指令可以看出,volatile變量在寫操作之前使用了lock前綴脓钾,lock前綴指令在多處理器下會(huì)引發(fā)兩件事情:

  1. 將當(dāng)前處理器的緩存行里的數(shù)據(jù)寫回到內(nèi)存售睹。lock前綴指令的在執(zhí)行期間會(huì)產(chǎn)生LOCK#信號(hào),該信號(hào)會(huì)鎖住總線可训,導(dǎo)致其他CPU不能訪問總線昌妹。由于鎖總線開銷比較大,在最近的處理器中握截,LOCK#信號(hào)不鎖總線了飞崖,而是鎖緩存并回寫到主存,用緩存一直性機(jī)制來確保修改的原子性(該操作又被稱之為緩存鎖定)谨胞;

  2. 寫回內(nèi)存的操作會(huì)使其他CPU的緩存無效固歪。IA-32處理器和Intel 64處理器使用MESI(修改,獨(dú)占畜眨,共享昼牛,無效)控制協(xié)議去維護(hù)內(nèi)部緩存和其他處理器緩存的一致性。IA-32和Intel 64處理器使用嗅探技術(shù)保證各處理器緩存和系統(tǒng)內(nèi)存的數(shù)據(jù)在總線上保持一致康聂。如果一個(gè)處理器通過嗅探檢測(cè)到其他處理器準(zhǔn)備寫內(nèi)存贰健,并且該內(nèi)存地址處于共享狀態(tài),該處理器會(huì)將它的緩存行置為無效恬汁,下次再操作該內(nèi)存地址時(shí)會(huì)重新把數(shù)據(jù)讀到緩存行中伶椿。

使用場(chǎng)景

多嘴兩句,volatile雖然在某些情況下性能要優(yōu)于synchronized氓侧,但是由于volatile無法保證操作的原子性脊另,所以volatile是無法替代synchronized的。

通常來說约巷,使用volatile必須具備以下2個(gè)條件:

  • 對(duì)變量的寫操作不依賴于當(dāng)前值偎痛,比如++操作,volatile不能保證多線程情況下操作的正確性的独郎;

  • 該變量沒有包含在具有其他變量的不變式中踩麦。

下面列舉兩個(gè)我們平常在工作中使用較多的場(chǎng)景:

  1. 狀態(tài)標(biāo)記
    需求描述:根據(jù)狀態(tài)標(biāo)記進(jìn)行消息發(fā)送枚赡;
    代碼示例:
public class MessageProcessor {

    private volatile boolean flag;

    public void process() {
        if (flag) {
            sendMessage();
        } else {
            recordLog();
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

用volatile修飾flag,如果有其他線程修改過該標(biāo)識(shí)谓谦,其他線程都可以拿到最新值贫橙,根據(jù)最新值各個(gè)線程可以進(jìn)行相關(guān)的操作。

  1. 單例模式的double check

    單例模式的double check

    很多人在使用double check的時(shí)候都不用volatile修飾反粥,程序也能正常運(yùn)行卢肃,但是其實(shí)不使用volatile修飾是有問題的。那會(huì)有什么問題呢才顿?
    其實(shí)會(huì)出問題的主要是instance = new Singleton();莫湘,它并不是一個(gè)原子操作,在JVM中娜膘,這行代碼主要完成了這樣3件事:

    a. 為對(duì)象分配內(nèi)存空間逊脯;

    b. 初始化對(duì)象;

    c. 將instance對(duì)象指向分配的內(nèi)存空間,執(zhí)行完這一步驟竣贪,instance就不為null了军洼。

    在指令重排序時(shí),a-b-c的順序可能會(huì)被重排成a-c-b演怎,如果現(xiàn)在執(zhí)行順序被重排成a-c-b匕争,在單線程情況下不會(huì)影響程序執(zhí)行的結(jié)果,但是在多線程情況下就不一樣了爷耀,如果線程A執(zhí)行了指令c甘桑,此時(shí)instance實(shí)例還沒有被初始化好,但是已經(jīng)不為null了歹叮,剛好線程B執(zhí)行到if (null == instance)跑杭,發(fā)現(xiàn)instance不為空,隨即返回咆耿,但是得到的卻是未被完全初始化的實(shí)例德谅,在使用的時(shí)候就會(huì)有問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萨螺,一起剝皮案震驚了整個(gè)濱河市窄做,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慰技,老刑警劉巖椭盏,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吻商,居然都是意外死亡掏颊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門艾帐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乌叶,“玉大人改化,你說我怎么就攤上這事⊥骰瑁” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵揍鸟,是天一觀的道長(zhǎng)兄裂。 經(jīng)常有香客問我,道長(zhǎng)阳藻,這世上最難降的妖魔是什么晰奖? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮腥泥,結(jié)果婚禮上匾南,老公的妹妹穿的比我還像新娘。我一直安慰自己蛔外,他們只是感情好蛆楞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夹厌,像睡著了一般豹爹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矛纹,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天臂聋,我揣著相機(jī)與錄音,去河邊找鬼或南。 笑死孩等,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的采够。 我是一名探鬼主播肄方,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼吁恍!你這毒婦竟也來了扒秸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤冀瓦,失蹤者是張志新(化名)和其女友劉穎伴奥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翼闽,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拾徙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了感局。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尼啡。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暂衡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崖瞭,到底是詐尸還是另有隱情狂巢,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布书聚,位于F島的核電站唧领,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雌续。R本人自食惡果不足惜斩个,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驯杜。 院中可真熱鬧受啥,春花似錦、人聲如沸鸽心。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽再悼。三九已至核畴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冲九,已是汗流浹背谤草。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莺奸,地道東北人丑孩。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像灭贷,于是被迫代替她去往敵國(guó)和親温学。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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