Volatile 關(guān)鍵字

命名

volatile 英文單詞的意思為“揮發(fā)性的,不穩(wěn)定的”如暖。
修飾變量后,說(shuō)明該變量是不穩(wěn)定的忌堂,對(duì)該變量的讀寫需要與主內(nèi)存交互盒至。即該變量在線程的工作內(nèi)存中揮發(fā)了,需要從主內(nèi)存中進(jìn)行讀取浸船。進(jìn)而保證的變量的可見(jiàn)性妄迁。

變量可見(jiàn)性問(wèn)題

在一個(gè)操作非volatile變量的多線程程序里,每個(gè)線程可能會(huì)從主內(nèi)存復(fù)制變量到CPU緩存以提高性能李命。如果計(jì)算機(jī)包含多個(gè)CPU登淘,每個(gè)線程可能運(yùn)行在不同CPU上。也就是說(shuō)封字,每個(gè)線程可能會(huì)復(fù)制變量到不同的CPU緩存中黔州。如下圖:

使用非volatile變量將不保證JVM什么時(shí)候會(huì)從主存讀變量到CPU緩存,或什么時(shí)候會(huì)將變量從CPU緩存寫到主存阔籽。

想象一下流妻,多個(gè)線程讀取同一個(gè)共享變量

public class SharedObject {

    public int counter = 0;

}

只有線程T1更新counter變量,但線程T1, T2都可能讀取counter變量笆制。如果counter變量未聲明為volatile绅这,將不能保證counter變量何時(shí)寫回到主存中,也就不能保證其他線程讀取的counter變量值的正確性在辆。如圖:

volatile 的可見(jiàn)性保證

可見(jiàn)性保證如下:

  • 如果線程A對(duì)一個(gè)volatile變量進(jìn)行寫证薇,隨后線程B讀取相同的volatile變量度苔,那么在寫volatile變量前的所有對(duì)線程A可見(jiàn)的變量,在線程B讀取該volatile變量后浑度,對(duì)線程B也是可見(jiàn)的寇窑。
  • 如果線程A讀取volatile變量,那么所有對(duì)線程A可見(jiàn)的變量將會(huì)從主內(nèi)存重新讀取箩张。

例子:

public class MyClass {
    private int years;
    private int months
    private volatile int days;


    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

update() 方法寫3個(gè)變量甩骏,其中days聲明為volatile

volatile 可見(jiàn)性保證:當(dāng)對(duì)days進(jìn)行寫時(shí)先慷,所有對(duì)該線程可見(jiàn)的變量(years, months)同樣會(huì)被寫進(jìn)主內(nèi)存饮笛。

同時(shí),當(dāng)讀取變量years, months, days時(shí)

    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

當(dāng)totalDays()方法讀取days變量论熙,months缎浇,years同樣也是從主內(nèi)存中讀取的。

指令重排的挑戰(zhàn)

因性能原因赴肚,Java VM與CPU允許以程序的指令進(jìn)行重排,只要語(yǔ)義不變二蓝。比如:

int a = 1;
int b = 2;

a++;
b++;

以上指令可能被重排誉券,但語(yǔ)義并沒(méi)有發(fā)生變化:

int a = 1;
a++;

int b = 2;
b++;

然而,指令重排給volatile變量帶來(lái)挑戰(zhàn)刊愚。讓我們重新看看這段代碼:

public class MyClass {
    private int years;
    private int months
    private volatile int days;


    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

當(dāng)對(duì)days進(jìn)行寫時(shí)踊跟,years, months同樣會(huì)被寫進(jìn)主內(nèi)存。但是鸥诽,如果JVM對(duì)指令進(jìn)行了重排商玫,如:

public void update(int years, int months, int days){
    this.days   = days;
    this.months = months;
    this.years  = years;
}

years, months在被賦予新值前就被寫進(jìn)主內(nèi)存,其他線程將不能讀取新值牡借。這時(shí)的指令重排就改變了語(yǔ)義拳昌。

volatile的Happens-before保證

為了解決指令重排的問(wèn)題,除了可見(jiàn)性保證外钠龙,Java volatile關(guān)鍵字還提供的happens-before保證炬藤。

  • 如果原先對(duì)其他變量的讀寫發(fā)生在寫volatile變量前,那么對(duì)其他變量的讀寫指令不能重排到寫volatile變量后碴里;
  • 如果原先對(duì)其他變量的讀發(fā)生在讀volatile變量后沈矿,那么對(duì)其他變量的讀指令不能重排到讀volatile變量前;

volatile 并不總是滿足的

即使volatile關(guān)鍵字保證所有對(duì)volatile變量的讀都直接從主內(nèi)存讀取咬腋,所有對(duì)volatile變量的寫都直接寫主內(nèi)存羹膳,仍然有定義一個(gè)變量為volatile不能夠滿足的情況。

在先前的例子中根竿,只有線程1對(duì)共享變量counter進(jìn)行定陵像,對(duì)counter定義為volatile變量能夠保證線程2問(wèn)題能看見(jiàn)最新的值就珠。

實(shí)際上,多線程甚至能寫一個(gè)共享的volatile變量而主內(nèi)存的值仍然是正確蠢壹,如果其新值不依賴于它先前的值嗓违。也就是說(shuō),如果線程不需要讀取變量先前的值去計(jì)算下一個(gè)值图贸。

只要一個(gè)線程需要先讀取volatile變量的值蹂季,然后基于該值生成一個(gè)新值,再寫回volatile變量疏日,那么volatile變量將不再足夠去保證可見(jiàn)性的正確偿洁。在讀寫volatile變量的時(shí)間間隙,產(chǎn)生了競(jìng)態(tài)條件 沟优,多個(gè)線程可能讀取相同的volatile變量的值涕滋,并生成新值,當(dāng)寫入主內(nèi)存時(shí)相互覆蓋彼此的值挠阁。
如:

volatile 何時(shí)是足夠的

前面提到宾肺,兩個(gè)線程同時(shí)讀寫一個(gè)共享變量,僅僅使用volatile關(guān)鍵字是不夠的侵俗。你需要使用synchronized保證讀寫變量的原子性锨用。讀寫volatile變量并不會(huì)阻塞線程的讀寫。因?yàn)檫@個(gè)原因隘谣,必須在臨界區(qū)使用synchronized增拥。

volatile 的性能考量

  • 對(duì)volatile變量的讀寫引起主內(nèi)存的讀寫
  • 讀寫主內(nèi)存比訪問(wèn)CPU緩存開(kāi)銷更大
  • 訪問(wèn)volatile變量會(huì)阻止指令重排,而指令重排是正常的性能提升技術(shù)
    綜上所述寻歧,只有當(dāng)你真的需要增強(qiáng)變量的可見(jiàn)性時(shí)掌栅,才使用volatile變量。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末码泛,一起剝皮案震驚了整個(gè)濱河市猾封,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弟晚,老刑警劉巖忘衍,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異卿城,居然都是意外死亡枚钓,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門瑟押,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搀捷,“玉大人,你說(shuō)我怎么就攤上這事∧壑郏” “怎么了氢烘?”我有些...
    開(kāi)封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)家厌。 經(jīng)常有香客問(wèn)我播玖,道長(zhǎng),這世上最難降的妖魔是什么饭于? 我笑而不...
    開(kāi)封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任蜀踏,我火速辦了婚禮,結(jié)果婚禮上掰吕,老公的妹妹穿的比我還像新娘果覆。我一直安慰自己,他們只是感情好殖熟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布局待。 她就那樣靜靜地躺著,像睡著了一般菱属。 火紅的嫁衣襯著肌膚如雪钳榨。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天纽门,我揣著相機(jī)與錄音重绷,去河邊找鬼。 笑死膜毁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愤钾。 我是一名探鬼主播瘟滨,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼能颁!你這毒婦竟也來(lái)了杂瘸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伙菊,失蹤者是張志新(化名)和其女友劉穎败玉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镜硕,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡运翼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兴枯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片血淌。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悠夯,到底是詐尸還是另有隱情癌淮,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布沦补,位于F島的核電站乳蓄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏夕膀。R本人自食惡果不足惜虚倒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望店诗。 院中可真熱鬧裹刮,春花似錦、人聲如沸庞瘸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)擦囊。三九已至违霞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞬场,已是汗流浹背买鸽。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贯被,地道東北人眼五。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像彤灶,于是被迫代替她去往敵國(guó)和親看幼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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