Java內存模型:看Java如何解決可見性和有序性問題

什么是java內存模型?

導致可見性的原因是緩存,導致有序性的原因是編譯優(yōu)化,那解決可見性,有序性最直接的辦法就是禁用緩存和編譯優(yōu)化,但是這樣問雖然解決了,我們程序的性能可就堪憂了.

合理的方案應該是按需禁用緩存以及編譯優(yōu)化,那么怎么做到按需禁用呢?對于并發(fā)程序,何時禁用緩存以及編譯優(yōu)化只有程序員知道,那所謂的"按需禁用"其實就是指按照程序員的要求來禁用,所以,為了解決可見性和有序性問題,只需要提供程序員按需禁用緩存和編譯優(yōu)化的方法即可.

java內存模型是個很復雜的規(guī)范,可以從不同的視角來解讀,站在我們這些程序員的視角,本質上可以理解為,java內存模型規(guī)范了JVM如何提供按需禁用和編譯優(yōu)化的方法.具體來說,這些方法包括volatile.synchronizedfinal三個關鍵字,以及六項Happens-Before規(guī)則,這也是重點.

使用volatile的困惑

volatile關鍵字并不是java語言的特產,古老的C語言里也有,它最原始的意義就是禁用CPU緩存.

例如,聲明一個volatile變量volatile int x = 0,它表達的是:告訴編譯器,對這個變量的讀寫,不能使用CPU緩存,必須從內存中讀取或者寫入.這個語義看上去相當明確,但是實際使用的時候卻會帶來困惑.

例如下面的代碼,假設線程A執(zhí)行writer()方法,按照volatile語義,會把變量"v = true" 寫入內存;假設線程B執(zhí)行reader()方法,同樣按照volatile語義,線程B會從內存中讀取變量v,如果線程B看到"v=true"時,那么線程B看到的變量x是多少?

直覺上,應該是42,但實際要看java版本,1.5之前x可能是42,也可能是0; 1.5以上版本運行x就是42.


分析一下,1.5之前版本出現(xiàn)x=0情況變量x可能被CPU緩存而導致可見性問題.這個問題在1.5版本已經被解決,java內存模型在1.5版本對volatile語義進行了增強.答案就是一項Happens-Before規(guī)則.

Happens-Before 規(guī)則

Happens-Before 并不是說前面一個操作發(fā)生在后續(xù)操作的前面,它真正要表達的是:前面一個操作的結果對后續(xù)操作是可見的茸俭。

比較正式的說法是:Happens-Before 約束了編譯器的優(yōu)化行為,雖允許編譯器優(yōu)化,但是要求編譯器優(yōu)化后一定遵守 Happens?Before 規(guī)則.

1. 程序的順序性規(guī)則

這條規(guī)則是指在一個線程中,按照程序順序,前面的操作Happens-Before于后續(xù)的任意操作.這還是比較容易理解的.比如剛才的代碼.按照程序的順序,第6行代碼"x=42"Happens-Before于第7行代碼"v=true",這就是規(guī)則1的內容,也比較符合單線程里面的思維:程序前面對某個變量的修改一定是對后續(xù)操作可見的.

2. volatile 變量規(guī)則

這條規(guī)則是指對一個volatile變量的寫操作,Happens-Before于后續(xù)對這個volatile變量的讀操作.

這個就有點費解了,對一個volatile變量的寫操作相對于后續(xù)對這個volatile變量的讀操作可見,這怎么看都是禁用緩存的意思啊.貌似和1.5版本之前的語義沒有變化?如果關聯(lián)規(guī)則3就有不一樣的感覺了!

3.傳遞性

這條規(guī)則如果A Happens-Before B,且B Happens-Before C 那么 A Happens-Before C.

應用到代碼中:

從圖中看出:

1.x=42 Happens-Before 寫變量v = true ,這是規(guī)則1.

2.寫變量v = true?Happens-Before 讀變量 v = true,這是規(guī)則2.

根據(jù)傳遞性,x = 42?Happens-Before 讀變量v = true.意味者如果線程B督導了v = true,那么線程A設置的x = 42 對線程B是可見的,也就是說,線程B能看到x == 42.這就是1.5版本對volatile語義的增強,1.5 版本的并發(fā)工具包(java.util.concurrent)就是靠 volatile 語義來搞定可見性的.

4. 管程中鎖的規(guī)則

這條規(guī)則是指對一個鎖的解鎖 Happens-Before 于后續(xù)對這個鎖的加鎖镶殷。

管程是一種通用的同步原語利花,在Java 中指的就是 synchronized聪全,synchronized 是 Java 里對管程的實現(xiàn)捶惜。

管程中的鎖在 Java 里是隱式實現(xiàn)的,例如下面的代碼荔烧,在進入同步塊之前,會自動加鎖汽久,而在代碼塊執(zhí)行完會自動釋放鎖鹤竭,加鎖以及釋放鎖都是編譯器幫我們實現(xiàn)的.

假設 x 的初始值是 10,線程 A 執(zhí)行完代碼塊后 x 的值會變成 12(執(zhí)行完自動釋放鎖)景醇,線程 B 進入代碼塊時臀稚,能夠看到線程 A 對 x 的寫操作,也就是線程 B 能夠看到 x==12三痰。

5. 線程 start() 規(guī)則

這條是關于線程啟動的吧寺。它是指主線程 A 啟動子線程 B 后,子線程 B 能夠看到主線程在啟動子線程 B 前的操作散劫。

換句話說就是稚机,如果線程 A 調用線程 B 的 start() 方法(即在線程 A 中啟動線程 B),那么該 start() 操作 Happens-Before 于線程 B 中的任意操作获搏。

6. 線程 join() 規(guī)則

這條是關于線程等待的.它指的是主線程A等待子線程B完成(主線程A通過調用子線程B的join()方法實現(xiàn)),但子線程B完成后(主線程A中join()方法返回),主線程能夠看到子線程的操作,看到指的是對共享變量的操作.

換句話說就是赖条,如果在線程 A 中,調用線程 B 的 join() 并成功返回常熙,那么線程 B 中的任意操作 Happens-Before 于該 join() 操作的返回纬乍。

被我們忽視的 final

volatile 為的是禁用緩存以及編譯優(yōu)化,

final 修飾變量時裸卫,初衷是告訴編譯器:這個變量生而不變仿贬,可以可勁兒優(yōu)化。Java 編譯器在 1.5 以前的版本的確優(yōu)化得很努力墓贿,以至于都優(yōu)化錯了茧泪。問題類似于上一期提到的利用雙重檢查方法創(chuàng)建單例蜓氨,構造函數(shù)的錯誤重排導致線程可能看到 final 變量的值會變化

在 1.5 以后 Java 內存模型對 final 類型變量的重排進行了約束

總結

在 Java 語言里面,Happens-Before 的語義本質上是一種可見性调炬,A Happens-Before B意味著 A 事件對 B 事件來說是可見的语盈,無論 A 事件和 B 事件是否發(fā)生在同一個線程里。例如 A 事件發(fā)生在線程 1 上缰泡,B 事件發(fā)生在線程 2 上刀荒,Happens-Before 規(guī)則保證線程 2上也能看到 A 事件的發(fā)生。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末棘钞,一起剝皮案震驚了整個濱河市缠借,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宜猜,老刑警劉巖泼返,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姨拥,居然都是意外死亡绅喉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門叫乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柴罐,“玉大人,你說我怎么就攤上這事憨奸「锿溃” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵排宰,是天一觀的道長似芝。 經常有香客問我,道長板甘,這世上最難降的妖魔是什么党瓮? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮盐类,結果婚禮上麻诀,老公的妹妹穿的比我還像新娘。我一直安慰自己傲醉,他們只是感情好蝇闭,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硬毕,像睡著了一般呻引。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吐咳,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天逻悠,我揣著相機與錄音元践,去河邊找鬼。 笑死童谒,一個胖子當著我的面吹牛单旁,可吹牛的內容都是我干的。 我是一名探鬼主播饥伊,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼象浑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琅豆?” 一聲冷哼從身側響起愉豺,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茫因,沒想到半個月后蚪拦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡冻押,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年驰贷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洛巢。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡饱苟,死狀恐怖,靈堂內的尸體忽然破棺而出狼渊,到底是詐尸還是另有隱情,我是刑警寧澤类垦,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布狈邑,位于F島的核電站,受9級特大地震影響蚤认,放射性物質發(fā)生泄漏米苹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一砰琢、第九天 我趴在偏房一處隱蔽的房頂上張望蘸嘶。 院中可真熱鬧,春花似錦陪汽、人聲如沸训唱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽况增。三九已至,卻和暖如春训挡,著一層夾襖步出監(jiān)牢的瞬間澳骤,已是汗流浹背歧强。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留为肮,地道東北人摊册。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像颊艳,于是被迫代替她去往敵國和親茅特。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容