Java 線程模型

Java 線程之間的通信對程序員完全透明岖是,內(nèi)存可見性問題很容易困擾 Java 程序員栏赴,本文將簡要介紹 Java 內(nèi)存模型。

Java 內(nèi)存模型的基礎(chǔ)

并發(fā)編程模型的兩個關(guān)鍵問題

在并發(fā)編程中毯炮,需要處理兩個關(guān)鍵問題:線程之間如何通信及線程之間如何同步省咨。通信是指線程之間以何種機制來交換信息肃弟。在命令式編程中,線程之間的通信機制有倆種:共享內(nèi)存和消息傳遞茸炒。

在共享內(nèi)存的并發(fā)模型里愕乎,線程之間共享程序的公共狀態(tài),通過寫-讀內(nèi)存中的公共狀態(tài)進行隱式通信壁公。在消息傳遞的并發(fā)模型里感论,線程之間沒有公共狀態(tài),線程之間必須通過發(fā)送消息來顯示通信紊册。

同步是指程序中用于控制不同線程間操作發(fā)生相對順序的機制比肄。在共享內(nèi)存并發(fā)模型里快耿,同步是顯示進行的。程序員必須顯示指定某個方法或某段代碼需要在線程之間互斥執(zhí)行芳绩。在消息傳遞的并發(fā)模型里掀亥,由于消息的發(fā)送必須在消息的接受之前,因此同步是隱式進行的妥色。

Java 的并發(fā)采用的是共享內(nèi)存模型搪花,Java 線程之間的通信是隱式進行的,整個通信過程對程序員完全透明嘹害,但是線程之間同步是顯式進行的撮竿,需要程序員顯式的進行線程同步。

Java 內(nèi)存模型的抽象結(jié)構(gòu)

在 Java 中笔呀,所有實例域幢踏、靜態(tài)域和數(shù)組元素都存儲在堆內(nèi)存中,堆內(nèi)存在線程之間共享许师。局部變量房蝉、方法定義參數(shù)和異常處理器參數(shù)不會在線程之間共享,它們不會有線程可見性問題微渠,也不受內(nèi)存模型的影響搭幻。

Java 線程之間的通信由 Java 內(nèi)存模型(JMM)控制, JMM 決定一個線程對共享變量的寫入何時對另一個線程可見敛助。從抽象的角度來看粗卜, JMM 定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存中屋确,每個線程都有一個私有本地內(nèi)存纳击,本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是 JMM 的一個抽象概念攻臀,并不真實存在焕数,它涵蓋了緩存、寫緩沖區(qū)刨啸、寄存器以及其他的硬件和編譯器優(yōu)化堡赔。Java 內(nèi)存模型的抽象示意圖如下:

Java 內(nèi)存模型的抽象結(jié)構(gòu)示意圖.png

從上圖來看,如果線程 A 與線程 B 之間要通信的話设联,必須要經(jīng)過下面兩個步驟善已。
1)線程 A 把本地內(nèi)存 A 中更新過的共享變量刷新到主存中去。
2)線程 B 到主存中去讀線程 A 之前已經(jīng)更新過的共享變量离例。

從源代碼到指令序列的重排序

在執(zhí)行程序時换团,為了提高性能,編譯器和處理器常常會對指令做重排序宫蛆。重排序分為三種類型艘包。

  • 編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序想虎。
  • 指令級并行的重排序∝宰穑現(xiàn)代處理器采用了指令級并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性舌厨,處理器可以改變語句對應(yīng)機器指令的執(zhí)行順序岂却。
  • 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū)裙椭,著使得加載和存儲操作看上去可能是在亂序執(zhí)行淌友。

上述的 1 屬于編譯器重排序,上述的 2 和 3 屬于處理器重排序骇陈。這些重排序可能會導致多線程出現(xiàn)內(nèi)存可見性問題震庭。對于編譯器, JMM 的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序你雌。對于處理器重排序器联, JMM 的處理器重排序規(guī)則會要求 Java 編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障指令婿崭,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序拨拓。

JMM 屬于語言級的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺之上氓栈,通過禁止特定類型的編譯器重排序和處理器重排序渣磷,為程序員提供一致的內(nèi)存可見性保證。

happens-before 簡介

JMM 中使用 happens-before 的概念來闡述操作之間的內(nèi)存可見性授瘦。在 JMM 中醋界,如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須要存在 happens-before 關(guān)系提完,這里提到的兩個操作既可以是在一個線程之內(nèi)形纺,也可以是在不同線程之間。
常見的 happens-before 規(guī)則如下:

  • 程序順序規(guī)則:一個線程中的每個操作 happens-before 于該線程中的任意后續(xù)操作徒欣。
  • 監(jiān)視器鎖規(guī)則:對一個鎖的解鎖 happens-before 于隨后對這個鎖的加鎖逐样。
  • volatile 規(guī)則:對于一個 volatile 域的寫 happens-before 于任意后續(xù)對這個 volatile 域的讀。對于 volatile 的詳細理解可以看我的另一篇文章打肝。
  • 傳遞性:如果 A happens-before B脂新,且 B happens-before C,那么 A happens-before C粗梭。

一個 happens-before 規(guī)則對應(yīng)于一個或多個編譯器和處理器重排序規(guī)則争便。對于 Java 程序員來說,happens-before 規(guī)則簡單易懂楼吃,它避免了 Java 程序員為了理解 JMM 提供的內(nèi)存可見性保證而去學習復雜的重排序規(guī)則以及這些規(guī)則的具體實現(xiàn)方法始花。

重排序

重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段妄讯。

數(shù)據(jù)依賴性

如果兩個操作訪問同一個變量,并且這兩個操作中有一個為寫操作酷宵,此時著兩個操作之間就存在數(shù)據(jù)依賴性亥贸。數(shù)據(jù)依賴性分為下面 3 種類型:

名稱 代碼示例 說明
寫后讀 a = 1;b = a浇垦; 寫一個變量之后炕置,再讀這個變量
寫后寫 a = 1; a = 2男韧; 寫一個變量之后再寫這個變量
讀后寫 a = b朴摊; b = 1; 讀一個變量之后再寫這個變量

上面 3 種情況此虑,只要重排序兩個操作的執(zhí)行順序甚纲,程序的執(zhí)行結(jié)果就會被改變。

前面提到過朦前,編譯器和處理器可能會對操作做重排序介杆。編譯器和處理器在重排序時會遵守數(shù)據(jù)依賴性,編譯器和處理器不會改變存在數(shù)據(jù)依賴關(guān)系的兩個操作的執(zhí)行順序韭寸。這里所說的數(shù)據(jù)依賴性僅針對單個處理器中執(zhí)行的指令序列和單個線程中執(zhí)行的操作春哨,不同的處理器和不同線程之間的數(shù)據(jù)依賴性不被編譯器和處理器考慮。

as-if-serial 語義

as-if-serial 語義的意思是:不管怎么重排序恩伺,(單線程)程序的執(zhí)行結(jié)果不能被改變赴背。編譯器、 runtime 和 處理器都必須遵守晶渠。

為了遵守 as-if-serial 語義凰荚,編譯器和處理器不會對存在數(shù)據(jù)依賴關(guān)系的操作做重排序,因為這種重排序會改變程序的執(zhí)行結(jié)果乱陡,但是如果操作之間不存在數(shù)據(jù)依賴關(guān)系浇揩,這些操作就可能被編譯器和處理器重排序。

double pi = 3.14;        // A
double r = 1.0;          // B
double area = pi * r * r;// C

上面的代碼片段中憨颠, A 和 C 之間存在數(shù)據(jù)依賴關(guān)系, B 和 C 之間也存在數(shù)據(jù)依賴關(guān)系积锅,因此在最終執(zhí)行的序列中爽彤, C 不能被重排序到 A 和 B 的前面,但 A 和 B 之間不存在數(shù)據(jù)依賴關(guān)系缚陷,編譯器和處理器可以重排序 A 和 B 之間的執(zhí)行順序适篙。

as-if-serial 語義把單線程程序保護了起來,遵守 as-if-serial 語義的編譯器箫爷、runtime 和處理器共同為編寫單線程程序的程序員創(chuàng)建了一個幻覺:單線程程序是按程序的順序來執(zhí)行的嚷节。as-if-serial 語義使單線程程序員無需擔心重排序會干擾他們聂儒,也無需擔心內(nèi)存可見性問題。

程序順序規(guī)則

根據(jù) happens-before 規(guī)則硫痰,上面計算圓的面積的示例代碼存在 3 個 happens-before 關(guān)系衩婚。

  • A happens-before B。
  • B happens-before C效斑。
  • A happens-before C非春。

這里 A happens-before B,但實際執(zhí)行時 B 卻可以排在 A 之前執(zhí)行缓屠。如果 A happens-before B奇昙,JMM 并不要求 A 一定要在 B 之前執(zhí)行, JMM 只要求前一個操作對后一個操作可見敌完,這里操作 A 的執(zhí)行結(jié)果不需要對操作 B 可見储耐,而且重排序操作 A 和操作 B的執(zhí)行結(jié)果與操作 A 和操作 B 按 happens-before 順序執(zhí)行的結(jié)果一致。在這種情況下滨溉, JMM 會認為這種重排序并不非法弧岳, JMM 允許這種重排序。

在計算機中业踏,軟件技術(shù)和硬件技術(shù)有一個共同目標:在不改變程序執(zhí)行結(jié)果的前提下禽炬,盡可能并行度。編譯器勤家、處理器和 JMM 都遵循這一規(guī)則腹尖。

順序一致性

順序一致性內(nèi)存模型是一個理論參考模型,在設(shè)計的時候伐脖,處理器的內(nèi)存模型和編程語言的內(nèi)存模型都會以順序一致性內(nèi)存模型作為參照热幔。

數(shù)據(jù)競爭與順序一致性

當程序未正確同步時,就可能會存在數(shù)據(jù)競爭讼庇。 JMM 對正確同步的多線程程序的內(nèi)存一致性做了如下保證绎巨。
如果程序是正確同步的,線程的執(zhí)行將具有順序一致性——即程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同蠕啄。

順序一致性內(nèi)存模型

順序一致性內(nèi)存模型是一個被計算機科學家理想化了的理論參考模型场勤,它為程序員提供了極強的內(nèi)存可見性保證。順序一致性內(nèi)存模型有倆大特性歼跟。

  • 一個線程中的所有操作必須按照程序的順序來執(zhí)行和媳。
  • 不管程序是否同步,所有線程都只能看到一個單一的操作執(zhí)行順序哈街,在順序一致性內(nèi)存模型中留瞳,每個操作都必須原子執(zhí)行且立刻對所有線程可見。

在概念上骚秦,順序一致性內(nèi)存模型有一個單一的全局內(nèi)存她倘,這個內(nèi)存通過一個左右擺動的開關(guān)可以連接到任意一個線程璧微,同時每一個線程必須按照程序的順序來執(zhí)行內(nèi)存讀/寫操作。

總結(jié)

前面對 Java 內(nèi)存模型的基礎(chǔ)知識和內(nèi)存模型的具體實現(xiàn)進行了說明硬梁。下面對 Java 內(nèi)存模型相關(guān)知識做一個總結(jié)前硫。

JMM 是一個語言級的內(nèi)存模型,處理器內(nèi)存模型是硬件級的內(nèi)存模型靶溜,順序一致性內(nèi)存模型是一個理論參考模型开瞭。 JMM 和處理器內(nèi)存模型在設(shè)計時通常會以順序一致性內(nèi)存模型作為參照,在設(shè)計時 JMM 和處理器內(nèi)存模型會對順序一致性模型做一些放松罩息,因為如果完全按照順序一致性模型來實現(xiàn) JMM 和處理器內(nèi)存模型嗤详,那么很多的編譯器和處理器優(yōu)化都要被禁止,這對程序的執(zhí)行性能將有很大的影響瓷炮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葱色,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子娘香,更是在濱河造成了極大的恐慌苍狰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烘绽,死亡現(xiàn)場離奇詭異淋昭,居然都是意外死亡,警方通過查閱死者的電腦和手機安接,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門翔忽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盏檐,你說我怎么就攤上這事歇式。” “怎么了胡野?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵材失,是天一觀的道長。 經(jīng)常有香客問我硫豆,道長龙巨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任够庙,我火速辦了婚禮恭应,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耘眨。我一直安慰自己,他們只是感情好境肾,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布剔难。 她就那樣靜靜地躺著胆屿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪偶宫。 梳的紋絲不亂的頭發(fā)上非迹,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天青自,我揣著相機與錄音铁追,去河邊找鬼。 笑死挠说,一個胖子當著我的面吹牛吵冒,可吹牛的內(nèi)容都是我干的纯命。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼痹栖,長吁一口氣:“原來是場噩夢啊……” “哼亿汞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揪阿,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤疗我,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后南捂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吴裤,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年溺健,在試婚紗的時候發(fā)現(xiàn)自己被綠了麦牺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡矿瘦,死狀恐怖枕面,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缚去,我是刑警寧澤潮秘,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站易结,受9級特大地震影響枕荞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搞动,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一躏精、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鹦肿,春花似錦矗烛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碌嘀。三九已至,卻和暖如春歪架,著一層夾襖步出監(jiān)牢的瞬間股冗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工和蚪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留止状,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓攒霹,卻偏偏與公主長得像怯疤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剔蹋,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

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