volatile 的語義

文章會在我的獨立博客同步更新

volatile 是 java 中一個非常常見神得,功能非常強(qiáng)大的一個關(guān)鍵字肥照,大家用的最多的地方可能就是單例模式的雙重檢查鎖的寫法中败富。提到 volatile 舟山,不得不提 synchronized 兔甘, synchronized 是一個重量級鎖谎碍,那么 volatile 是一個輕量級鎖嗎?并不是洞焙, volatile 是一個輕量級的同步關(guān)鍵字蟆淀,那么 volatile 的語義到底是什么呢?這就是這篇文章要介紹的內(nèi)容澡匪。

從單例模式的雙重檢查鎖寫法說起

首先我們看一下常見的單例模式的雙重檢查鎖寫法熔任。更多單例模式的寫法

public class SingleInstance {
    private static SingleInstance sSingleInstance;

    private SingleInstance() {
    }

    public static SingleInstance getInstance() {
        if (sSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (sSingleInstance == null) {
                    sSingleInstance = new SingleInstance();
                }
            }
        }
        return sSingleInstance;
    }
}

上面這種寫法在 《Java 并發(fā)編程實戰(zhàn)》 一書中的評價是【臭名昭著】 ,可見這種寫法是有非常大的問題唁情,不過幸運的是我們有 volatile 關(guān)鍵字疑苔,在 Java 5 以上將 sSingleInstance 用 volatile 關(guān)鍵字修飾就可以解決(具體問題我們下面再討論)。 那么 volatile 為什么能解決問題甸鸟, volatile 又為程序員保證了什么惦费,這就是下面要討論的問題兵迅。

可見性

在 JMM 中,為了提高程序性能薪贫,線程對于變量的讀寫不會直接作用于主存恍箭,而是會先作用于相對應(yīng)的本地內(nèi)存,最后會在合適的時機(jī)再同步到主存后雷。而 volatile 的可見性是指季惯,線程每次在更新本地內(nèi)存的變量之后,會同步刷新到主存中去臀突,同樣的線程每次在讀 volatile 變量時都會將本地內(nèi)存中的值置為無效勉抓。然后線程會直接去主存中讀取相應(yīng)的值 。下面我們用一個例子再加深點認(rèn)識候学。

//程序片段1
class VolatileExample{
    int a = 0;
    boolean flag = false;

    public void write(){
        a = 1;          //1
        flag = true;    //2
    }

    public void read(){
        if(flag){
            int i = a;  //3
            ...         //4
        }
    }

}

假設(shè) write 方法在 線程 A 中執(zhí)行藕筋, read 方法在線程 B 中執(zhí)行,我們不考慮其他因素(重排序)梳码,假設(shè) A 線程先執(zhí)行隐圾, B 線程后執(zhí)行,那么當(dāng) B 線程執(zhí)行時掰茶, B 線程能否正確讀取到 flag 和 a 的值呢暇藏?很可惜,答案是不一定濒蒋。因為 JVM 不保證何時會將本地內(nèi)存中的值同步到主存中去盐碱。如果我們將程序改成下面這樣,結(jié)果又是如何呢沪伙?

//程序片段2
class VolatileExample{
    int a = 0;
    volatile boolean flag = false;

    public void write(){
        a = 1;          //1
        flag = true;    //2
    }

    public void read(){
        if(flag){
            int i = a;  //3
            ...         //4
        }
    }

}

同樣的我們暫時不考慮重排序瓮顽,將 flag 使用 volatile 修飾之后, volatile 可以保證在線程 A 修改了 falg 值之后會將 flag 的值同步到主存中去围橡,同樣的在 B 線程讀取 flag 的時候也會去主存中讀取暖混。那么我們還有一個問題,這個時候雖然線程 B 可以正確讀取到 flag 的值翁授,那么線程 B 還能正確讀取到 a 的值嗎拣播?答案是:可以。 volatile 會保證在同步 flag 的值到主存的同時會將寫 volatile 變量之前的操作同時同步到主存中去收擦。同樣的當(dāng)線程 B 開始去主存中讀取 volatile 時贮配,也會去主存中讀取 a 的值。
JMM 的抽象示意圖如下:

JMM-ABS.png

阻止重排序

重排序:編譯器和處理器有可能會對不存在數(shù)據(jù)依賴的兩條指令進(jìn)行重排序炬守,這里的數(shù)據(jù)依賴僅指單線程或單個處理器牧嫉。還是以上面的程序片段1為例,重排序是指 1 和 2 以及 3 和 4 的執(zhí)行順序不可預(yù)測『ㄔ澹可能的執(zhí)行順序有:1->2->3->4;2->1->3->4;1->2->4->3;2->1->4->3;
通過將程序片段1改為程序片段2就可以阻止重排序曹洽,最終的執(zhí)行結(jié)果就是:1->2->3->4 ;
為了實現(xiàn) volatile 內(nèi)存語義, JMM 針對編譯器制定的重排序規(guī)則如下:

reorder_rule.png

從上表我們可以看出:

  1. 當(dāng)?shù)诙€操作是 volatile 寫時辽剧,不管第一個操作是什么送淆,都不會重排序
  2. 當(dāng)?shù)谝粋€操作是 volatile 讀時,不管第一個操作是什么怕轿,都不會重排序
  3. 當(dāng)?shù)谝粋€操作是 volatile 寫偷崩,第二個操作是 volatile 讀是不會重排序

再探雙重檢查鎖

下面我們再來看看為什么下面這種雙重檢查鎖寫法就不是【臭名昭著】的了呢?更多單例模式的寫法

public class SingleInstance {
    private static volatile SingleInstance sSingleInstance;

    private SingleInstance() {
    }

    public static SingleInstance getInstance() {
        if (sSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (sSingleInstance == null) {
                    sSingleInstance = new SingleInstance();
                }
            }
        }
        return sSingleInstance;
    }
}

首先撞羽,我們要討論的是開頭的那種寫法存在的問題阐斜。
其實 sSingleInstance = new SingleInstance(); 這句代碼看起來只有一句,但是被編譯成指令是3句诀紊,分別是

  1. 為 SingleInstance 分配內(nèi)存空間
  2. 調(diào)用 SingleInstance 的構(gòu)造函數(shù)谒出,初始化成員變量
  3. 為 sSingleInstance 賦值

根據(jù)前面的知識我們知道第 2 步和第 3 步可能會重排序,這樣的話有可能某個線程獲取到的單例就是未完全初始化的實例邻奠,為了解決這個問題笤喳,我們用 volatile 修飾 sSingleInstance 之后,根據(jù)上面的阻止重排序規(guī)則我們知道 volatile 寫和前面的一條指令不會進(jìn)行重排序碌宴,所以也就不會有問題了杀狡,這就是 volatile 的妙用。其實 volatile 遠(yuǎn)不止這點用處贰镣,在 Java 提供的并發(fā)包中有很多工具類的實現(xiàn)基礎(chǔ)就是 volatile 呜象,這些大家可以進(jìn)一步了解。

參考文獻(xiàn)

Java Memory Model
Java Volatile Keyword

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末八孝,一起剝皮案震驚了整個濱河市董朝,隨后出現(xiàn)的幾起案子鸠项,更是在濱河造成了極大的恐慌干跛,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祟绊,死亡現(xiàn)場離奇詭異楼入,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)牧抽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門嘉熊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扬舒,你說我怎么就攤上這事阐肤。” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵孕惜,是天一觀的道長愧薛。 經(jīng)常有香客問我,道長衫画,這世上最難降的妖魔是什么毫炉? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮削罩,結(jié)果婚禮上瞄勾,老公的妹妹穿的比我還像新娘。我一直安慰自己弥激,他們只是感情好进陡,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著微服,像睡著了一般四濒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上职辨,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天盗蟆,我揣著相機(jī)與錄音,去河邊找鬼舒裤。 笑死喳资,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腾供。 我是一名探鬼主播仆邓,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伴鳖!你這毒婦竟也來了节值?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤榜聂,失蹤者是張志新(化名)和其女友劉穎搞疗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體须肆,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡匿乃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了豌汇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幢炸。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拒贱,靈堂內(nèi)的尸體忽然破棺而出宛徊,到底是詐尸還是另有隱情佛嬉,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布闸天,位于F島的核電站巷燥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏号枕。R本人自食惡果不足惜缰揪,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望葱淳。 院中可真熱鬧钝腺,春花似錦、人聲如沸赞厕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皿桑。三九已至毫目,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诲侮,已是汗流浹背镀虐。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留沟绪,地道東北人刮便。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像绽慈,于是被迫代替她去往敵國和親恨旱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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