volatile原理

通過前面一章我們了解了synchronized是一個(gè)重量級(jí)的鎖,雖然JVM對(duì)它做了很多優(yōu)化,而下面介紹的volatile則是輕量級(jí)的synchronized映穗。如果一個(gè)變量使用volatile荆烈,則它比使用synchronized的成本更加低,因?yàn)樗粫?huì)引起線程上下文的切換和調(diào)度裸燎。Java語言規(guī)范對(duì)volatile的定義如下:

Java編程語言允許線程訪問共享變量顾瞻,為了確保共享變量能被準(zhǔn)確和一致地更新,線程應(yīng)該確保通過排他鎖單獨(dú)獲得這個(gè)變量德绿。

上面比較繞口荷荤,通俗點(diǎn)講就是說一個(gè)變量如果用volatile修飾了,則Java可以確保所有線程看到這個(gè)變量的值是一致的移稳,如果某個(gè)線程對(duì)volatile修飾的共享變量進(jìn)行更新蕴纳,那么其他線程可以立馬看到這個(gè)更新,這就是所謂的線程可見性个粱。

volatile雖然看起來比較簡(jiǎn)單古毛,使用起來無非就是在一個(gè)變量前面加上volatile即可,但是要用好并不容易(LZ承認(rèn)我至今仍然使用不好都许,在使用時(shí)仍然是模棱兩可)稻薇。

內(nèi)存模型相關(guān)概念

理解volatile其實(shí)還是有點(diǎn)兒難度的,它與Java的內(nèi)存模型有關(guān)梭稚,所以在理解volatile之前我們需要先了解有關(guān)Java內(nèi)存模型的概念颖低,這里只做初步的介紹,后續(xù)LZ會(huì)詳細(xì)介紹Java內(nèi)存模型弧烤。

操作系統(tǒng)語義

計(jì)算機(jī)在運(yùn)行程序時(shí)忱屑,每條指令都是在CPU中執(zhí)行的蹬敲,在執(zhí)行過程中勢(shì)必會(huì)涉及到數(shù)據(jù)的讀寫。我們知道程序運(yùn)行的數(shù)據(jù)是存儲(chǔ)在主存中莺戒,這時(shí)就會(huì)有一個(gè)問題伴嗡,讀寫主存中的數(shù)據(jù)沒有CPU中執(zhí)行指令的速度快,如果任何的交互都需要與主存打交道則會(huì)大大影響效率从铲,所以就有了CPU高速緩存瘪校。CPU高速緩存為某個(gè)CPU獨(dú)有,只與在該CPU運(yùn)行的線程有關(guān)名段。

有了CPU高速緩存雖然解決了效率問題阱扬,但是它會(huì)帶來一個(gè)新的問題:數(shù)據(jù)一致性。在程序運(yùn)行中伸辟,會(huì)將運(yùn)行所需要的數(shù)據(jù)復(fù)制一份到CPU高速緩存中麻惶,在進(jìn)行運(yùn)算時(shí)CPU不再也主存打交道,而是直接從高速緩存中讀寫數(shù)據(jù)信夫,只有當(dāng)運(yùn)行結(jié)束后才會(huì)將數(shù)據(jù)刷新到主存中窃蹋。舉一個(gè)簡(jiǎn)單的例子:

i++

當(dāng)線程運(yùn)行這段代碼時(shí),首先會(huì)從主存中讀取i( i = 1)静稻,然后復(fù)制一份到CPU高速緩存中警没,然后CPU執(zhí)行 + 1 (2)的操作,然后將數(shù)據(jù)(2)寫入到告訴緩存中振湾,最后刷新到主存中杀迹。其實(shí)這樣做在單線程中是沒有問題的,有問題的是在多線程中恰梢。如下:

假如有兩個(gè)線程A佛南、B都執(zhí)行這個(gè)操作(i++),按照我們正常的邏輯思維主存中的i值應(yīng)該=3嵌言,但事實(shí)是這樣么嗅回?分析如下:

兩個(gè)線程從主存中讀取i的值(1)到各自的高速緩存中,然后線程A執(zhí)行+1操作并將結(jié)果寫入高速緩存中摧茴,最后寫入主存中绵载,此時(shí)主存i==2,線程B做同樣的操作,主存中的i仍然=2苛白。所以最終結(jié)果為2并不是3娃豹。這種現(xiàn)象就是緩存一致性問題。

解決緩存一致性方案有兩種:

  1. 通過在總線加LOCK#鎖的方式

  2. 通過緩存一致性協(xié)議

但是方案1存在一個(gè)問題购裙,它是采用一種獨(dú)占的方式來實(shí)現(xiàn)的懂版,即總線加LOCK#鎖的話,只能有一個(gè)CPU能夠運(yùn)行躏率,其他CPU都得阻塞躯畴,效率較為低下民鼓。

第二種方案,緩存一致性協(xié)議(MESI協(xié)議)它確保每個(gè)緩存中使用的共享變量的副本是一致的蓬抄。其核心思想如下:當(dāng)某個(gè)CPU在寫數(shù)據(jù)時(shí)丰嘉,如果發(fā)現(xiàn)操作的變量是共享變量,則會(huì)通知其他CPU告知該變量的緩存行是無效的嚷缭,因此其他CPU在讀取該變量時(shí)饮亏,發(fā)現(xiàn)其無效會(huì)重新從主存中加載數(shù)據(jù)。

image

Java內(nèi)存模型

上面從操作系統(tǒng)層次闡述了如何保證數(shù)據(jù)一致性阅爽,下面我們來看一下Java內(nèi)存模型路幸,稍微研究一下Java內(nèi)存模型為我們提供了哪些保證以及在Java中提供了哪些方法和機(jī)制來讓我們?cè)谶M(jìn)行多線程編程時(shí)能夠保證程序執(zhí)行的正確性。

在并發(fā)編程中我們一般都會(huì)遇到這三個(gè)基本概念:原子性付翁、可見性劝赔、有序性。我們稍微看下volatile

原子性

原子性:即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷胆敞,要么就都不執(zhí)行。

原子性就像數(shù)據(jù)庫里面的事務(wù)一樣杂伟,他們是一個(gè)團(tuán)隊(duì)移层,同生共死。其實(shí)理解原子性非常簡(jiǎn)單赫粥,我們看下面一個(gè)簡(jiǎn)單的例子即可:

i = 0;            ---1
j = i ;            ---2
i++;            ---3
i = j + 1;    ---4

上面四個(gè)操作观话,有哪個(gè)幾個(gè)是原子操作,那幾個(gè)不是越平?如果不是很理解频蛔,可能會(huì)認(rèn)為都是原子性操作,其實(shí)只有1才是原子操作秦叛,其余均不是晦溪。

1---在Java中,對(duì)基本數(shù)據(jù)類型的變量和賦值操作都是原子性操作挣跋;

2---包含了兩個(gè)操作:讀取i三圆,將i值賦值給j

3---包含了三個(gè)操作:讀取i值、i + 1 避咆、將+1結(jié)果賦值給i舟肉;

4---同三一樣

在單線程環(huán)境下我們可以認(rèn)為整個(gè)步驟都是原子性操作,但是在多線程環(huán)境下則不同查库,Java只保證了基本數(shù)據(jù)類型的變量和賦值操作才是原子性的(注:在32位的JDK環(huán)境下路媚,對(duì)64位數(shù)據(jù)的讀取不是原子性操作,如long樊销、double*)整慎。要想在多線程環(huán)境下保證原子性脏款,則可以通過鎖、synchronized來確保院领。

volatile是無法保證復(fù)合操作的原子性

可見性

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

在上面已經(jīng)分析了丈氓,在多線程環(huán)境下,一個(gè)線程對(duì)共享變量的操作對(duì)其他線程是不可見的强法。

Java提供了volatile來保證可見性万俗。

當(dāng)一個(gè)變量被volatile修飾后,表示著線程本地內(nèi)存無效饮怯,當(dāng)一個(gè)線程修改共享變量后他會(huì)立即被更新到主內(nèi)存中闰歪,當(dāng)其他線程讀取共享變量時(shí),它會(huì)直接從主內(nèi)存中讀取蓖墅。

當(dāng)然库倘,synchronize和鎖都可以保證可見性。

有序性

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

在Java內(nèi)存模型中教翩,為了效率是允許編譯器和處理器對(duì)指令進(jìn)行重排序,當(dāng)然重排序它不會(huì)影響單線程的運(yùn)行結(jié)果贪壳,但是對(duì)多線程會(huì)有影響饱亿。

Java提供volatile來保證一定的有序性。最著名的例子就是單例模式里面的DCL(雙重檢查鎖)闰靴。這里L(fēng)Z就不再闡述了彪笼。

剖析volatile原理

JMM比較龐大,不是上面一點(diǎn)點(diǎn)就能夠闡述的蚂且。上面簡(jiǎn)單地介紹都是為了volatile做鋪墊的配猫。

volatile可以保證線程可見性且提供了一定的有序性,但是無法保證原子性杏死。在JVM底層volatile是采用“內(nèi)存屏障”來實(shí)現(xiàn)的章姓。

上面那段話,有兩層語義

  1. 保證可見性识埋、不保證原子性

  2. 禁止指令重排序

第一層語義就不做介紹了凡伊,下面重點(diǎn)介紹指令重排序。

在執(zhí)行程序時(shí)為了提高性能窒舟,編譯器和處理器通常會(huì)對(duì)指令做重排序:

  1. 編譯器重排序系忙。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序惠豺;

  2. 處理器重排序银还。如果不存在數(shù)據(jù)依賴性风宁,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序;

指令重排序?qū)尉€程沒有什么影響蛹疯,他不會(huì)影響程序的運(yùn)行結(jié)果戒财,但是會(huì)影響多線程的正確性。既然指令重排序會(huì)影響到多線程執(zhí)行的正確性捺弦,那么我們就需要禁止重排序饮寞。那么JVM是如何禁止重排序的呢?這個(gè)問題稍后回答列吼,我們先看另一個(gè)原則happens-before幽崩,happen-before原則保證了程序的“有序性”,它規(guī)定如果兩個(gè)操作的執(zhí)行順序無法從happens-before原則中推到出來寞钥,那么他們就不能保證有序性慌申,可以隨意進(jìn)行重排序。其定義如下:

  1. 同一個(gè)線程中的理郑,前面的操作 happen-before 后續(xù)的操作蹄溉。(即單線程內(nèi)按代碼順序執(zhí)行。但是您炉,在不影響在單線程環(huán)境執(zhí)行結(jié)果的前提下类缤,編譯器和處理器可以進(jìn)行重排序,這是合法的邻吭。換句話說,這一是規(guī)則無法保證編譯重排和指令重排)宴霸。

  2. 監(jiān)視器上的解鎖操作 happen-before 其后續(xù)的加鎖操作囱晴。(Synchronized 規(guī)則)

  3. 對(duì)volatile變量的寫操作 happen-before 后續(xù)的讀操作。(volatile 規(guī)則)

  4. 線程的start() 方法 happen-before 該線程所有的后續(xù)操作瓢谢。(線程啟動(dòng)規(guī)則)

  5. 線程所有的操作 happen-before 其他線程在該線程上調(diào)用 join 返回成功后的操作畸写。

  6. 如果 a happen-before b,b happen-before c氓扛,則a happen-before c(傳遞性)枯芬。

我們著重看第三點(diǎn)volatile規(guī)則:對(duì)volatile變量的寫操作 happen-before 后續(xù)的讀操作。為了實(shí)現(xiàn)volatile內(nèi)存語義采郎,JMM會(huì)重排序千所,其規(guī)則如下:

對(duì)happen-before原則有了稍微的了解,我們?cè)賮砘卮疬@個(gè)問題JVM是如何禁止重排序的蒜埋?

image

觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時(shí)所生成的匯編代碼發(fā)現(xiàn)淫痰,加入volatile關(guān)鍵字時(shí),會(huì)多出一個(gè)lock前綴指令整份。lock前綴指令其實(shí)就相當(dāng)于一個(gè)內(nèi)存屏障待错。內(nèi)存屏障是一組處理指令籽孙,用來實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制。volatile的底層就是通過內(nèi)存屏障來實(shí)現(xiàn)的火俄。下圖是完成上述規(guī)則所需要的內(nèi)存屏障:

volatile暫且下分析到這里犯建,JMM體系較為龐大,不是三言兩語能夠說清楚的瓜客,后面會(huì)結(jié)合JMM再一次對(duì)volatile深入分析适瓦。

image

作者:chenssy
鏈接:http://www.reibang.com/p/fb334c1f35ea
來源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)忆家,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處犹菇。</pre>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芽卿,隨后出現(xiàn)的幾起案子揭芍,更是在濱河造成了極大的恐慌,老刑警劉巖卸例,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件称杨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡筷转,警方通過查閱死者的電腦和手機(jī)姑原,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呜舒,“玉大人锭汛,你說我怎么就攤上這事∠龋” “怎么了唤殴?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)到腥。 經(jīng)常有香客問我朵逝,道長(zhǎng),這世上最難降的妖魔是什么乡范? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任配名,我火速辦了婚禮,結(jié)果婚禮上晋辆,老公的妹妹穿的比我還像新娘渠脉。我一直安慰自己,他們只是感情好瓶佳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布连舍。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪索赏。 梳的紋絲不亂的頭發(fā)上盼玄,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音潜腻,去河邊找鬼埃儿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛融涣,可吹牛的內(nèi)容都是我干的童番。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼威鹿,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼剃斧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忽你,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤幼东,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后科雳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體根蟹,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年糟秘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了简逮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尿赚,死狀恐怖散庶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凌净,我是刑警寧澤悲龟,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站泻蚊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丑婿。R本人自食惡果不足惜性雄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羹奉。 院中可真熱鬧秒旋,春花似錦、人聲如沸诀拭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耕挨。三九已至细卧,卻和暖如春尉桩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贪庙。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工蜘犁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人止邮。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓这橙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親导披。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屈扎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • 計(jì)算機(jī)內(nèi)存模型 計(jì)算機(jī)在執(zhí)行程序時(shí),每條指令都是在CPU中執(zhí)行的撩匕,而執(zhí)行指令過程中鹰晨,勢(shì)必涉及到數(shù)據(jù)的讀取和寫入。由...
    azmohan閱讀 325評(píng)論 0 1
  • volatile 關(guān)鍵字解析 原文出處: 海子volatile 這個(gè)關(guān)鍵字可能很多朋友都聽說過滑沧,或許也都用過并村。在 ...
    常青大俠閱讀 581評(píng)論 0 4
  • 用 volatile 修飾的變量能夠保證其對(duì)所有線程的可見性,要理解這一點(diǎn)滓技,我們首先需要了解 Java 的內(nèi)存模型...
    EricAlpha閱讀 1,804評(píng)論 19 25
  • 有時(shí)候我在想令漂,為什么堅(jiān)持一件事情膝昆,就那么那么難,難道真的是基因問題嗎叠必?或許是有一點(diǎn)點(diǎn)吧荚孵,可那畢竟是次要因素。 很小...
    無清寒閱讀 365評(píng)論 0 0
  • 假如你想成為一個(gè)數(shù)據(jù)科學(xué)家纬朝,或者已經(jīng)是數(shù)據(jù)科學(xué)家的你想擴(kuò)展你的技能收叶,那么你已經(jīng)來對(duì)地方了。本文的目的就是給數(shù)據(jù)分析...
    韓立鋒閱讀 594評(píng)論 0 5