volatile關(guān)鍵字

參考鏈接:http://www.infoq.com/cn/articles/java-memory-model-4/
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
http://blog.csdn.net/feier7501/article/details/20001083

在Java的內(nèi)存模式下准浴,線程就把變量保存到本地的內(nèi)存中,不直接和主存進(jìn)行讀寫感论。這樣就可能會出現(xiàn)有一個線程在貯存中修改了某變量奈揍,但是另一個線程讀取的是它之前在本地內(nèi)存里的拷貝值曲尸。這樣就會造成數(shù)據(jù)不一致了。

volatile寫-讀的內(nèi)存語義#

volatile寫的內(nèi)存語義如下:

當(dāng)寫一個volatile變量時男翰,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存另患。

假設(shè)線程A首先執(zhí)行writer()方法,隨后線程B執(zhí)行reader()方法蛾绎,初始時兩個線程的本地內(nèi)存中的flag和a都是初始狀態(tài)昆箕。下圖是線程A執(zhí)行volatile寫后,共享變量的狀態(tài)示意圖:

線程A在寫flag變量后租冠,本地內(nèi)存A中被線程A更新過的兩個共享變量的值被刷新到主內(nèi)存中鹏倘。此時,本地內(nèi)存A和主內(nèi)存中的共享變量的值是一致的顽爹。

volatile讀的內(nèi)存語義如下:

當(dāng)讀一個volatile變量時纤泵,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量话原。
下面是線程B讀同一個volatile變量后夕吻,共享變量的狀態(tài)示意圖:

如上圖所示,在讀flag變量后繁仁,本地內(nèi)存B已經(jīng)被置為無效涉馅。此時,線程B必須從主內(nèi)存中讀取共享變量黄虱。線程B的讀取操作將導(dǎo)致本地內(nèi)存B與主內(nèi)存中的共享變量的值也變成一致的了稚矿。

如果我們把volatile寫和volatile讀這兩個步驟綜合起來看的話,在讀線程B讀一個volatile變量后捻浦,寫線程A在寫這個volatile變量之前所有可見的共享變量的值都將立即變得對讀線程B可見晤揣。

我以前看了這些之后就一直以為volatile是原子性的,可以實現(xiàn)線程同步的朱灿,事實上并不是這樣的C潦丁!##

Volatile 變量可用于提供線程安全盗扒,但是只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當(dāng)前值與修改后值之間沒有約束跪楞。因此,單獨使用 volatile 還不足以實現(xiàn)計數(shù)器侣灶、互斥鎖或任何具有與多個變量相關(guān)的不變式(Invariants)的類(例如 “start <=end”)甸祭。

正確使用 volatile 變量的條件#

只能在有限的一些情形下使用 volatile 變量替代鎖(如synchronized)。要使 volatile 變量提供理想的線程安全褥影,必須同時滿足下面兩個條件:

1)對變量的寫操作不依賴于當(dāng)前值池户。
2)該變量沒有包含在具有其他變量的不變式中。

實際上凡怎,這些條件表明校焦,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀態(tài),包括變量的當(dāng)前狀態(tài)栅贴。

第一個條件的限制使 volatile 變量不能用作線程安全計數(shù)器斟湃。雖然增量操作(x++)看上去類似一個單獨操作,實際上它是一個由讀乳苁怼-修改-寫入操作序列組成的組合操作凝赛,必須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性坛缕。實現(xiàn)正確的操作需要使 x 的值在操作期間保持不變墓猎,而 volatile 變量無法實現(xiàn)這點。(然而赚楚,如果將值調(diào)整為只從單個線程寫入毙沾,那么可以忽略第一個條件。)

大多數(shù)編程情形都會與這兩個條件的其中之一沖突宠页,使得 volatile 變量不能像 synchronized 那樣普遍適用于實現(xiàn)線程安全左胞。

性能考慮#

使用 volatile 變量的主要原因是其簡易性:
在某些情形下寇仓,使用 volatile 變量要比使用相應(yīng)的鎖簡單得多。使用 volatile 變量次要原因是其性能:某些情況下烤宙,volatile 變量同步機(jī)制的性能要優(yōu)于鎖遍烦。

很難做出準(zhǔn)確、全面的評價躺枕,例如 “X 總是比 Y 快”服猪,尤其是對 JVM 內(nèi)在的操作而言。(例如拐云,某些情況下 VM 也許能夠完全刪除鎖機(jī)制罢猪,這使得我們難以抽象地比較 volatile 和 synchronized 的開銷。)

就是說叉瘩,在目前大多數(shù)的處理器架構(gòu)上膳帕,volatile 讀操作開銷非常低 —— 幾乎和非 volatile 讀操作一樣。而 volatile 寫操作的開銷要比非 volatile 寫操作多很多房揭,因為要保證可見性需要實現(xiàn)內(nèi)存界定(Memory Fence)备闲,即便如此,volatile 的總開銷仍然要比鎖獲取低捅暴。

volatile 操作不會像鎖一樣造成阻塞恬砂,因此,在能夠安全使用 volatile 的情況下蓬痒,volatile 可以提供一些優(yōu)于鎖的可伸縮特性泻骤。

如果讀操作的次數(shù)要遠(yuǎn)遠(yuǎn)超過寫操作,與鎖相比梧奢,volatile 變量通常能夠減少同步的性能開銷狱掂。

原文地址是:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
上鏈接原文里提到了很多模式(而我還沒有明白的看懂:-O)

關(guān)于volatile的重排列#

為了實現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時亲轨,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序趋惨。對于編譯器來說,發(fā)現(xiàn)一個最優(yōu)布置來最小化插入屏障的總數(shù)幾乎不可能惦蚊,為此器虾,JMM采取保守策略。下面是基于保守策略的JMM內(nèi)存屏障插入策略:

在每個volatile寫操作的前面插入一個StoreStore屏障蹦锋。
在每個volatile寫操作的后面插入一個StoreLoad屏障兆沙。
在每個volatile讀操作的前面插入一個LoadLoad屏障。
在每個volatile讀操作的后面插入一個LoadStore屏障莉掂。
上述內(nèi)存屏障插入策略非常保守葛圃,但它可以保證在任意處理器平臺,任意的程序中都能得到正確的volatile內(nèi)存語義。

就是這樣:

StoreStore屏障在volatile寫之前库正,這樣前面的所有普通寫操作已經(jīng)對任意處理器可見了曲楚。這是因為StoreStore屏障將保障上面所有的普通寫在volatile寫之前刷新到主內(nèi)存。

這里比較有意思的是volatile寫后面的StoreLoad屏障褥符。這個屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序洞渤。因為編譯器常常無法準(zhǔn)確判斷在一個volatile寫的后面,是否需要插入一個StoreLoad屏障(比如属瓣,一個volatile寫之后方法立即return)。為了保證能正確實現(xiàn)volatile的內(nèi)存語義讯柔,JMM在這里采取了保守策略:在每個volatile寫的后面或在每個volatile讀的前面插入一個StoreLoad屏障抡蛙。從整體執(zhí)行效率的角度考慮,JMM選擇了在每個volatile寫的后面插入一個StoreLoad屏障魂迄。因為volatile寫-讀內(nèi)存語義的常見使用模式是:一個寫線程寫volatile變量粗截,多個讀線程讀同一個volatile變量。當(dāng)讀線程的數(shù)量大大超過寫線程時捣炬,選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執(zhí)行效率的提升熊昌。從這里我們可以看到JMM在實現(xiàn)上的一個特點:首先確保正確性,然后再去追求執(zhí)行效率湿酸。

上圖中的LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序婿屹。LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序。

這里有一段示例代碼:

class VolatileBarrierExample {
    int a;
    volatile int v1 = 1;
    volatile int v2 = 2;

    void readAndWrite() {
        int i = v1;           //第一個volatile讀
        int j = v2;           // 第二個volatile讀
        a = i + j;            //普通寫
        v1 = i + 1;          // 第一個volatile寫
        v2 = j * 2;          //第二個 volatile寫
    }

    …                    //其他方法
}

針對readAndWrite()方法推溃,編譯器在生成字節(jié)碼時可以做如下的優(yōu)化:

注意昂利,最后的StoreLoad屏障不能省略。因為第二個volatile寫之后铁坎,方法立即return蜂奸。此時編譯器可能無法準(zhǔn)確斷定后面是否會有volatile讀或?qū)懀瑸榱税踩鹨娪财迹幾g器常常會在這里插入一個StoreLoad屏障扩所。

以上大多數(shù)內(nèi)容都來自http://www.infoq.com/cn/articles/java-memory-model-4/#anch95647
博主寫的尤為精彩,尤其是評論里的交流也值得一看朴乖。

在評論 區(qū)里看到一個 解答了自己的困惑:

當(dāng)讀一個volatile變量時祖屏,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。#

問題:設(shè)線程A和B共享變量x寒砖,線程B和C共享變量y赐劣,x和y是非volatile的,A和B線程之間共享volatile變量v哩都,那么當(dāng)B讀取v的時候魁兼,B線程的本地內(nèi)存里面的x被設(shè)為無效了,這點我理解,問題是咐汞,y是否也會被設(shè)為無效從而需要到主存中重新讀雀呛簟?

以下為博主的回答:

y也會被設(shè)為無效,從而需要到主存中重新讀取.

其實:本地內(nèi)存化撕,主內(nèi)存几晤,設(shè)置本地內(nèi)存為無效,從主內(nèi)存中去讀取值植阴。這些都是為了讓讀者更形象生動的理解java內(nèi)存模型而虛構(gòu)出來的蟹瘾,并不真實存在。
對于你的問題掠手,可以從volatile的編譯器重排序規(guī)則和volatile的處理器內(nèi)存屏障插入策略中找到答案憾朴。比如下面的程序代碼:

int i = volatile; //1,volatile讀
int j = a; //2,普通讀(假設(shè)a為普通共享變量)

在這里,不管a是在哪些線程之間共享喷鸽,volatile的編譯器重排序規(guī)則和volatile的處理器內(nèi)存屏障插入策略都會禁止2重排序到1的前面众雷。

關(guān)于好多JMM,甚至volatile本身我也沒有百分百看懂 做祝。此篇僅為學(xué)習(xí)記錄砾省,存在大量知識是我摘抄的~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市混槐,隨后出現(xiàn)的幾起案子编兄,更是在濱河造成了極大的恐慌,老刑警劉巖声登,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翻诉,死亡現(xiàn)場離奇詭異,居然都是意外死亡捌刮,警方通過查閱死者的電腦和手機(jī)碰煌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绅作,“玉大人芦圾,你說我怎么就攤上這事《砣希” “怎么了个少?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長眯杏。 經(jīng)常有香客問我夜焦,道長,這世上最難降的妖魔是什么岂贩? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任茫经,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卸伞。我一直安慰自己抹镊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布荤傲。 她就那樣靜靜地躺著垮耳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遂黍。 梳的紋絲不亂的頭發(fā)上终佛,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音雾家,去河邊找鬼查蓉。 笑死,一個胖子當(dāng)著我的面吹牛榜贴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妹田,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唬党,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鬼佣?” 一聲冷哼從身側(cè)響起驶拱,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晶衷,沒想到半個月后蓝纲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡晌纫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年税迷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锹漱。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡箭养,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哥牍,到底是詐尸還是另有隱情毕泌,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布嗅辣,位于F島的核電站撼泛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏澡谭。R本人自食惡果不足惜愿题,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抠忘,春花似錦撩炊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至囚灼,卻和暖如春骆膝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灶体。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工阅签, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝎抽。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓政钟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親樟结。 傳聞我的和親對象是個殘疾皇子养交,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

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

  • volatile 關(guān)鍵字解析 原文出處: 海子volatile 這個關(guān)鍵字可能很多朋友都聽說過,或許也都用過瓢宦。在 ...
    常青大俠閱讀 589評論 0 4
  • 文.孫亮好想給你一絲溫柔把自己化作秋風(fēng)去輕輕的挽住你的手你在左我漫步在右我就像滑落的樹葉把所有的真情獻(xiàn)給大地一分不...
    朦朧詩人孫亮閱讀 430評論 1 18
  • 寫在前面 目前我正在寫成都18樓的小仙滇-藏-川游記驮履,這次旅程已經(jīng)結(jié)束鱼辙,這次旅行時間是從3月5號開始,一直到4月1...
    長腳溫柔閱讀 888評論 10 3
  • 哎玫镐,我是不是有社交障礙暗瓜贰?我不是一個主動的人恐似,不會積極地與別人攀談聊天峭梳,除非工作上有聯(lián)系,否則只會呆在自己的圈子里...
    Sara_馨閱讀 191評論 0 0
  • abstract 抽象的abstract base class (ABC)抽象基類abstract class...
    碼蟻Q閱讀 997評論 3 29