Double-Check中的volatile作用

轉(zhuǎn)載自【張新強(qiáng)】的博客

看似完美的單例模式?

public class Single {
    private static Single3 instance;
    private Single() {}
    public static Single getInstance() {
        if (instance == null) {
            synchronized (Single.class) {
                if (instance == null) {
                    instance = new Single3();
                }
            }
        }
        return instance;
    }
}
  • 第一個(gè)if (instance == null),其實(shí)是為了解決效率問(wèn)題缠沈,只有instance為null的時(shí)候,才進(jìn)入synchronized的代碼段——大大減少了幾率错蝴。
  • 第二個(gè)if (instance == null)洲愤,則是為了防止可能出現(xiàn)多個(gè)實(shí)例的情況。

那么還會(huì)有問(wèn)題嗎顷锰?

答:只是『看起來(lái)』柬赐,還是有小概率出現(xiàn)問(wèn)題的。

原子操作

原子操作(atomic)就是不可分割的操作官紫,在計(jì)算機(jī)中肛宋,就是指不會(huì)因?yàn)?strong>線程調(diào)度被打斷的操作。

例子:

m = 6; // 這是個(gè)原子操作

假如m原先的值為0束世,那么對(duì)于這個(gè)操作酝陈,要么執(zhí)行成功m變成了6,要么是沒(méi)執(zhí)行m還是0毁涉,而不會(huì)出現(xiàn)諸如m=3這種中間態(tài)——即使是在并發(fā)的線程中沉帮。

而,聲明并賦值就不是一個(gè)原子操作:

int n = 6; // 這不是一個(gè)原子操作

對(duì)于這個(gè)語(yǔ)句贫堰,至少有兩個(gè)操作:
① 明一個(gè)變量n
② 給n賦值為6
——這樣就會(huì)有一個(gè)中間狀態(tài):變量n已經(jīng)被聲明了但是還沒(méi)有被賦值的狀態(tài)穆壕。
——這樣,在多線程中其屏,由于線程執(zhí)行順序的不確定性喇勋,如果兩個(gè)線程都使用m,就可能會(huì)導(dǎo)致不穩(wěn)定的結(jié)果出現(xiàn)偎行。

指令重排

概念

簡(jiǎn)單來(lái)說(shuō)川背,就是計(jì)算機(jī)為了提高執(zhí)行效率贰拿,會(huì)做的一些優(yōu)化,在不影響最終結(jié)果的情況下渗常,可能會(huì)對(duì)一些語(yǔ)句的執(zhí)行順序進(jìn)行調(diào)整。

例子:

int a ;   // 語(yǔ)句1 
a = 8 ;   // 語(yǔ)句2
int b = 9 ;     // 語(yǔ)句3
int c = a + b ; // 語(yǔ)句4

正常來(lái)說(shuō)皱碘,對(duì)于順序結(jié)構(gòu)询一,執(zhí)行的順序是自上到下,也即1234癌椿。
但是健蕊,由于指令重排的原因,因?yàn)?strong>不影響最終的結(jié)果踢俄,所以缩功,實(shí)際執(zhí)行的順序可能會(huì)變成3124或者1324
由于語(yǔ)句34沒(méi)有原子性的問(wèn)題都办,語(yǔ)句3和語(yǔ)句4也可能會(huì)拆分成原子操作嫡锌,再重排
——也就是說(shuō)琳钉,

對(duì)于非原子性的操作势木,在不影響最終結(jié)果的情況下,其拆分成的原子操作可能會(huì)被重新排列執(zhí)行順序歌懒。

回到話題

主要在于singleton = new Singleton()這句啦桌,這并非是一個(gè)原子操作,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情:

  1. 給 singleton 分配內(nèi)存
  2. 調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量及皂,形成實(shí)例
  3. 將singleton對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null 了)

但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化甫男。也就是說(shuō)上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2验烧。如果是后者板驳,則在 3 執(zhí)行完畢、2 未執(zhí)行之前碍拆,被線程二搶占了笋庄,這時(shí) instance 已經(jīng)是非 null 了(但卻沒(méi)有初始化),所以線程二會(huì)直接返回 instance倔监,然后使用,然后順理成章地報(bào)錯(cuò)菌仁。

錯(cuò)誤根源:

再稍微解釋一下浩习,就是說(shuō),由于有一個(gè)『instance已經(jīng)不為null但是仍沒(méi)有完成初始化』的中間狀態(tài)济丘,而這個(gè)時(shí)候谱秽,如果有其他線程剛好運(yùn)行到第一層if (instance == null)這里洽蛀,這里讀取到的instance已經(jīng)不為null了,所以就直接把這個(gè)中間狀態(tài)的instance拿去用了疟赊,就會(huì)產(chǎn)生問(wèn)題郊供。這里的關(guān)鍵在于——線程T1對(duì)instance的寫操作沒(méi)有完成,線程T2就執(zhí)行了讀操作近哟。

完全版

public class Single {
    private static volatile Single4 instance;
    private Single() {}
    public static Single getInstance() {
        if (instance == null) {
            synchronized (Single.class) {
                if (instance == null) {
                    instance = new Single();
                }
            }
        }
        return instance;
    }
}

volatile發(fā)揮的作用

volatile關(guān)鍵字的一個(gè)作用是禁止指令重排驮审,把instance聲明為volatile之后,對(duì)它的寫操作就會(huì)有一個(gè)內(nèi)存屏障(什么是內(nèi)存屏障吉执?)疯淫,這樣,在它的賦值完成之前戳玫,就不用會(huì)調(diào)用讀操作熙掺。

注意:volatile阻止的不是singleton = new Singleton()這句話內(nèi)部[1-2-3]的指令重排而是保證了在一個(gè)寫操作([1-2-3])完成之前咕宿,不會(huì)調(diào)用讀操作(if (instance == null))币绩。

最后編輯于
?著作權(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)離奇詭異,居然都是意外死亡川队,警方通過(guò)查閱死者的電腦和手機(jī)力细,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)固额,“玉大人眠蚂,你說(shuō)我怎么就攤上這事《孵铮” “怎么了逝慧?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)啄糙。 經(jīng)常有香客問(wèn)我笛臣,道長(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)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼尖淘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起著觉,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤村生,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(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
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至廷蓉,卻和暖如春全封,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桃犬。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工刹悴, 沒(méi)想到剛下飛機(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)容

  • volatile關(guān)鍵字經(jīng)常在并發(fā)編程中使用妒御,其特性是保證可見(jiàn)性以及有序性,但是關(guān)于volatile的使用仍然要小心...
    Ruheng閱讀 10,043評(píng)論 40 135
  • 從三月份找實(shí)習(xí)到現(xiàn)在镇饺,面了一些公司乎莉,掛了不少,但最終還是拿到小米奸笤、百度惋啃、阿里、京東揭保、新浪肥橙、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,207評(píng)論 11 349
  • 有個(gè)國(guó)度叫快速國(guó)秸侣,那的一天只有三個(gè)小時(shí)存筏,吃飯爸爸用一分半,媽媽用一分四十味榛,兒子用兩分鐘椭坚。他們每分鐘的心跳是200下...
    晨光微曉閱讀 388評(píng)論 0 0
  • APP的數(shù)據(jù)指標(biāo)體系主要分為五個(gè)維度,包括用戶規(guī)模與質(zhì)量搏色、參與度分析善茎、渠道分析、功能分析以用戶屬性分析频轿。用戶規(guī)模和...
    lj神經(jīng)刀閱讀 1,249評(píng)論 0 12
  • 好多天沒(méi)寫了垂涯,最近迷上了麻將烁焙,天天忙著頂。不出錯(cuò)是最好的
    宋世巍閱讀 87評(píng)論 0 0