再有人問你volatile是什么汹押,就把這篇文章發(fā)給他

Java語(yǔ)言為了解決并發(fā)編程中存在的原子性矿筝、可見性和有序性問題,提供了一系列和并發(fā)處理相關(guān)的關(guān)鍵字棚贾,比如synchronized窖维、volatile、final鸟悴、concurren包等陈辱。在前一篇文章中,我們也介紹了synchronized的用法及原理细诸。本文,來(lái)分析一下另外一個(gè)關(guān)鍵字——volatile陋守。

本文就圍繞volatile展開震贵,主要介紹volatile的用法、volatile的原理水评,以及volatile是如何提供可見性和有序性保障的等猩系。

volatile這個(gè)關(guān)鍵字,不僅僅在Java語(yǔ)言中有中燥,在很多語(yǔ)言中都有的寇甸,而且其用法和語(yǔ)義也都是不盡相同的。尤其在C語(yǔ)言疗涉、C++以及Java中拿霉,都有volatile關(guān)鍵字。都可以用來(lái)聲明變量或者對(duì)象咱扣。下面簡(jiǎn)單來(lái)介紹一下Java語(yǔ)言中的volatile關(guān)鍵字绽淘。

volatile的用法

volatile通常被比喻成"輕量級(jí)的synchronized",也是Java并發(fā)編程中比較重要的一個(gè)關(guān)鍵字闹伪。和synchronized不同沪铭,volatile是一個(gè)變量修飾符,只能用來(lái)修飾變量偏瓤。無(wú)法修飾方法及代碼塊等杀怠。

volatile的用法比較簡(jiǎn)單,只需要在聲明一個(gè)可能被多線程同時(shí)訪問的變量時(shí)厅克,使用volatile修飾就可以了赔退。

public class Singleton {

private volatile static Singleton singleton;

private Singleton (){}

public static Singleton getSingleton() {

if (singleton == null) {

synchronized (Singleton.class) {

if (singleton == null) {

singleton = new Singleton();

}

}

}

return singleton;

}

}

如以上代碼,是一個(gè)比較典型的使用雙重鎖校驗(yàn)的形式實(shí)現(xiàn)單例的已骇,其中使用volatile關(guān)鍵字修飾可能被多個(gè)線程同時(shí)訪問到的singleton离钝。

volatile的原理

在再有人問你Java內(nèi)存模型是什么票编,就把這篇文章發(fā)給他中我們?cè)?jīng)介紹過(guò),為了提高處理器的執(zhí)行速度卵渴,在處理器和內(nèi)存之間增加了多級(jí)緩存來(lái)提升慧域。但是由于引入了多級(jí)緩存,就存在緩存數(shù)據(jù)不一致問題浪读。

但是昔榴,對(duì)于volatile變量,當(dāng)對(duì)volatile變量進(jìn)行寫操作的時(shí)候碘橘,JVM會(huì)向處理器發(fā)送一條lock前綴的指令互订,將這個(gè)緩存中的變量回寫到系統(tǒng)主存中。

但是就算寫回到內(nèi)存痘拆,如果其他處理器緩存的值還是舊的仰禽,再執(zhí)行計(jì)算操作就會(huì)有問題,所以在多處理器下纺蛆,為了保證各個(gè)處理器的緩存是一致的吐葵,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議

緩存一致性協(xié)議:每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改桥氏,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài)温峭,當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里字支。

所以凤藏,如果一個(gè)變量被volatile所修飾的話,在每次數(shù)據(jù)變化之后堕伪,其值都會(huì)被強(qiáng)制刷入主存揖庄。而其他處理器的緩存由于遵守了緩存一致性協(xié)議,也會(huì)把這個(gè)變量的值從主存加載到自己的緩存中刃跛。這就保證了一個(gè)volatile在并發(fā)編程中抠艾,其值在多個(gè)緩存中是可見的。

volatile與可見性

可見性是指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí)桨昙,一個(gè)線程修改了這個(gè)變量的值检号,其他線程能夠立即看得到修改的值。

我們?cè)谠儆腥藛柲鉐ava內(nèi)存模型是什么蛙酪,就把這篇文章發(fā)給他中分析過(guò):Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中齐苛,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝桂塞,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行凹蜂,而不能直接讀寫主內(nèi)存。不同的線程之間也無(wú)法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行玛痊。所以汰瘫,就可能出現(xiàn)線程1改了某個(gè)變量的值,但是線程2不可見的情況擂煞。

前面的關(guān)于volatile的原理中介紹過(guò)了混弥,Java中的volatile關(guān)鍵字提供了一個(gè)功能,那就是被其修飾的變量在被修改后可以立即同步到主內(nèi)存对省,被其修飾的變量在每次是用之前都從主內(nèi)存刷新蝗拿。因此,可以使用volatile來(lái)保證多線程操作時(shí)變量的可見性蒿涎。

volatile與有序性

有序性即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行哀托。

我們?cè)谠儆腥藛柲鉐ava內(nèi)存模型是什么,就把這篇文章發(fā)給他中分析過(guò):除了引入了時(shí)間片以外劳秋,由于處理器優(yōu)化和指令重排等仓手,CPU還可能對(duì)輸入代碼進(jìn)行亂序執(zhí)行,比如load->add->save 有可能被優(yōu)化成load->save->add 玻淑。這就是可能存在有序性問題俗或。

而volatile除了可以保證數(shù)據(jù)的可見性之外,還有一個(gè)強(qiáng)大的功能岁忘,那就是他可以禁止指令重排優(yōu)化等。

普通的變量?jī)H僅會(huì)保證在該方法的執(zhí)行過(guò)程中所依賴的賦值結(jié)果的地方都能獲得正確的結(jié)果区匠,而不能保證變量的賦值操作的順序與程序代碼中的執(zhí)行順序一致干像。

volatile可以禁止指令重排,這就保證了代碼的程序會(huì)嚴(yán)格按照代碼的先后順序執(zhí)行驰弄。這就保證了有序性麻汰。被volatile修飾的變量的操作,會(huì)嚴(yán)格按照代碼順序執(zhí)行戚篙,load->add->save 的執(zhí)行順序就是:load五鲫、add、save岔擂。

volatile與原子性

原子性是指一個(gè)操作是不可中斷的位喂,要全部執(zhí)行完成,要不就都不執(zhí)行乱灵。

我們?cè)贘ava的并發(fā)編程中的多線程問題到底是怎么回事兒中分析過(guò):線程是CPU調(diào)度的基本單位塑崖。CPU有時(shí)間片的概念,會(huì)根據(jù)不同的調(diào)度算法進(jìn)行線程調(diào)度痛倚。當(dāng)一個(gè)線程獲得時(shí)間片之后開始執(zhí)行规婆,在時(shí)間片耗盡之后,就會(huì)失去CPU使用權(quán)。所以在多線程場(chǎng)景下抒蚜,由于時(shí)間片在線程間輪換掘鄙,就會(huì)發(fā)生原子性問題。

在上一篇文章中嗡髓,我們介紹synchronized的時(shí)候操漠,提到過(guò),為了保證原子性器贩,需要通過(guò)字節(jié)碼指令monitorenter和monitorexit颅夺,但是volatile和這兩個(gè)指令之間是沒有任何關(guān)系的。

所以蛹稍,volatile是不能保證原子性的吧黄。

在以下兩個(gè)場(chǎng)景中可以使用volatile來(lái)代替synchronized:

1、運(yùn)算結(jié)果并不依賴變量的當(dāng)前值唆姐,或者能夠確保只有單一的線程會(huì)修改變量的值拗慨。

2、變量不需要與其他狀態(tài)變量共同參與不變約束奉芦。

除以上場(chǎng)景外赵抢,都需要使用其他方式來(lái)保證原子性,如synchronized或者concurrent包声功。

我們來(lái)看一下volatile和原子性的例子:

public class Test {

public volatile int i = 0;

public void increase() {

i++;

}

public static void main(String[] args) {

final Test test = new Test();

for(int i=0;i<10;i++){

new Thread(){

public void run() {

for(int j=0;j<1000;j++)

test.increase();

};

}.start();

}

while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完

Thread.yield();

System.out.println(test.i);

}

}

以上代碼比較簡(jiǎn)單烦却,就是創(chuàng)建10個(gè)線程,然后分別執(zhí)行1000次i++操作先巴。正常情況下其爵,程序的輸出結(jié)果應(yīng)該是10000,但是伸蚯,多次執(zhí)行的結(jié)果都小于10000摩渺。這其實(shí)就是volatile無(wú)法滿足原子性的原因。

為什么會(huì)出現(xiàn)這種情況呢剂邮,那就是因?yàn)殡m然volatile可以保證i在多個(gè)線程之間的可見性摇幻。但是無(wú)法保證i++的原子性。

i++操作挥萌,一共有三個(gè)步驟:load i 绰姻,add i ,save i。在多線程場(chǎng)景中瑞眼,如果這三個(gè)步驟無(wú)法按照順序執(zhí)行的話龙宏,那么就會(huì)出現(xiàn)問題。

如上圖伤疙,兩個(gè)線程同時(shí)執(zhí)行i++操作银酗,如果允許指令重排辆影,我們期望的結(jié)果是3,但是實(shí)際執(zhí)行結(jié)果可能是2黍特,甚至可能是1蛙讥。

總結(jié)與思考

我們介紹過(guò)了volatile關(guān)鍵字和synchronized關(guān)鍵字。現(xiàn)在我們知道灭衷,synchronized可以保證原子性次慢、有序性和可見性。而volatile卻只能保證有序性和可見性翔曲。在這里順便給大家推薦一個(gè)架構(gòu)交流群:617434785迫像,里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis瞳遍,Netty源碼分析闻妓,高并發(fā)、高性能掠械、分布式由缆、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識(shí)體系猾蒂。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源均唉。相信對(duì)于已經(jīng)工作和遇到技術(shù)瓶頸的碼友,在這個(gè)群里會(huì)有你需要的內(nèi)容肚菠。

那么舔箭,我們?cè)賮?lái)看一下雙重校驗(yàn)鎖實(shí)現(xiàn)的單例,已經(jīng)使用了synchronized蚊逢,為什么還需要volatile限嫌?

public class Singleton {

private volatile static Singleton singleton;

private Singleton (){}

public static Singleton getSingleton() {

if (singleton == null) {

synchronized (Singleton.class) {

if (singleton == null) {

singleton = new Singleton();

}

}

}

return singleton;

}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市时捌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌炉抒,老刑警劉巖奢讨,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異焰薄,居然都是意外死亡拿诸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門塞茅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亩码,“玉大人,你說(shuō)我怎么就攤上這事野瘦∶韫担” “怎么了飒泻?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吏廉。 經(jīng)常有香客問我泞遗,道長(zhǎng),這世上最難降的妖魔是什么席覆? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任史辙,我火速辦了婚禮,結(jié)果婚禮上佩伤,老公的妹妹穿的比我還像新娘聊倔。我一直安慰自己,他們只是感情好生巡,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布耙蔑。 她就那樣靜靜地躺著,像睡著了一般障斋。 火紅的嫁衣襯著肌膚如雪纵潦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天垃环,我揣著相機(jī)與錄音邀层,去河邊找鬼。 笑死遂庄,一個(gè)胖子當(dāng)著我的面吹牛寥院,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涛目,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼秸谢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了霹肝?” 一聲冷哼從身側(cè)響起估蹄,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沫换,沒想到半個(gè)月后臭蚁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讯赏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年垮兑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漱挎。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡系枪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磕谅,到底是詐尸還是另有隱情私爷,我是刑警寧澤雾棺,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站当犯,受9級(jí)特大地震影響垢村,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚎卫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一嘉栓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拓诸,春花似錦侵佃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至倍谜,卻和暖如春迈螟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尔崔。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工答毫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人季春。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓洗搂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親载弄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耘拇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

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