Java volatile如何防止指令重排序

voliate關鍵字的兩個作用

1、 保證變量的可見性:當一個被volatile關鍵字修飾的變量被一個線程修改的時候毙石,其他線程可以立刻得到修改之后的結果蛋勺。當一個線程向被volatile關鍵字修飾的變量寫入數(shù)據(jù)的時候凹髓,虛擬機會強制它被值刷新到主內(nèi)存中。當一個線程用到被volatile關鍵字修飾的值的時候奶陈,虛擬機會強制要求它從主內(nèi)存中讀取易阳。
2、 屏蔽指令重排序:指令重排序是編譯器和處理器為了高效對程序進行優(yōu)化的手段尿瞭,它只能保證程序執(zhí)行的結果時正確的闽烙,但是無法保證程序的操作順序與代碼順序一致。這在單線程中不會構成問題声搁,但是在多線程中就會出現(xiàn)問題黑竞。非常經(jīng)典的例子是在單例方法中同時對字段加入voliate,就是為了防止指令重排序疏旨。

編譯期重排序的典型就是通過調(diào)整指令順序很魂,做到在不改變程序語義的前提下,盡可能減少寄存器的讀取檐涝、存儲次數(shù)遏匆,充分復用寄存器的存儲值
比如我們有如下代碼:

int x = 10;
int y = 9;
x = x+10;

假設編譯器直接對上面代碼進行編譯谁榜,不進行重排序的話幅聘,我們簡單分析一下執(zhí)行這段代碼的過程,首先加載x變量的內(nèi)存地址到地址寄存器窃植,然后會加載10到數(shù)據(jù)寄存器帝蒿,然后CPU通過mov指令把10寫入到地址寄存器中指定的內(nèi)存地址中。然后加載y變量的內(nèi)存地址到地址寄存器巷怜,加載9到數(shù)據(jù)寄存器葛超,把9寫入到內(nèi)存地址中暴氏。進行第三行執(zhí)行時,我們發(fā)現(xiàn)CPU需要重新加載x的內(nèi)存地址和數(shù)據(jù)到寄存器绣张,但如果我把第三行和第二行換一下順序答渔,那么執(zhí)行過程中對于寄存器的存取就可以少很多次,同時對于程序結果沒有任何影響侥涵。

另一個例子可以看下面的雙重檢查鎖構造單例的代碼

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) { // 1
            synchronized(Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton(); // 2
                }
            }
        }
        return singleton;
    }
} 

實際上當程序執(zhí)行到2處的時候沼撕,如果我們沒有使用volatile關鍵字修飾變量singleton,就可能會造成錯誤独令。這是因為使用new關鍵字初始化一個對象的過程并不是一個原子的操作端朵,它分成下面三個步驟進行:

a. 給 singleton 分配內(nèi)存
b. 調(diào)用 Singleton 的構造函數(shù)來初始化成員變量
c. 將 singleton 對象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton 就為非 null 了)

如果虛擬機存在指令重排序優(yōu)化,則步驟b和c的順序是無法確定的燃箭。如果A線程率先進入同步代碼塊并先執(zhí)行了c而沒有執(zhí)行b,此時因為singleton已經(jīng)非null舍败。這時候線程B到了1處招狸,判斷singleton非null并將其返回使用,因為此時Singleton實際上還未初始化邻薯,自然就會出錯裙戏。synchronized可以解決內(nèi)存可見性,但是不能解決重排序問題厕诡。

但是特別注意在jdk 1.5以前的版本使用了volatile的雙檢鎖還是有問題的累榜。其原因是Java 5以前的JMM(Java 內(nèi)存模型)是存在缺陷的,即時將變量聲明成volatile也不能完全避免重排序灵嫌,主要是volatile變量前后的代碼仍然存在重排序問題壹罚。這個volatile屏蔽重排序的問題在jdk 1.5 (JSR-133)中才得以修復,這時候jdk對volatile增強了語義寿羞,對volatile對象都會加入讀寫的內(nèi)存屏障猖凛,以此來保證可見性,這時候2-3就變成了代碼序而不會被CPU重排绪穆,所以在這之后才可以放心使用volatile辨泳。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市玖院,隨后出現(xiàn)的幾起案子菠红,更是在濱河造成了極大的恐慌,老刑警劉巖难菌,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件试溯,死亡現(xiàn)場離奇詭異,居然都是意外死亡扔傅,警方通過查閱死者的電腦和手機耍共,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門烫饼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人试读,你說我怎么就攤上這事杠纵。” “怎么了钩骇?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵比藻,是天一觀的道長。 經(jīng)常有香客問我倘屹,道長银亲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任纽匙,我火速辦了婚禮务蝠,結果婚禮上,老公的妹妹穿的比我還像新娘烛缔。我一直安慰自己馏段,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布践瓷。 她就那樣靜靜地躺著院喜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晕翠。 梳的紋絲不亂的頭發(fā)上喷舀,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音淋肾,去河邊找鬼硫麻。 笑死,一個胖子當著我的面吹牛巫员,可吹牛的內(nèi)容都是我干的庶香。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼简识,長吁一口氣:“原來是場噩夢啊……” “哼赶掖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起七扰,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奢赂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颈走,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膳灶,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了轧钓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片序厉。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖毕箍,靈堂內(nèi)的尸體忽然破棺而出弛房,到底是詐尸還是另有隱情,我是刑警寧澤而柑,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布文捶,位于F島的核電站,受9級特大地震影響媒咳,放射性物質(zhì)發(fā)生泄漏粹排。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一涩澡、第九天 我趴在偏房一處隱蔽的房頂上張望顽耳。 院中可真熱鬧,春花似錦妙同、人聲如沸斧抱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弄抬,卻和暖如春茎辐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掂恕。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工拖陆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懊亡。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓依啰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親店枣。 傳聞我的和親對象是個殘疾皇子速警,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359