【JVM盲點補漏系列】「并發(fā)編程的難題和挑戰(zhàn)」深入理解JMM及JVM內存模型

并發(fā)編程的難題和挑戰(zhàn)

在并發(fā)編程的技術領域中交惯,對于我們而言的難題主要有兩個:

  1. 多線程之間如何進行通信和線程之間如何同步乘盼,通信是指線程之間以何種機制來交換信息。

多線程的線程通信機制

在命令式編程中货葬,線程之間的通信機制有兩種:共享內存消息傳遞财搁。

  • 共享內存的方式,多線程之間共享公共的狀態(tài)(變量)慕购,那么線程之間通過寫/讀內存中的公共狀態(tài)(變量)來隱式進行通信聊疲。在此模式下,同步實現(xiàn)是隱式進行的沪悲,由于消息的發(fā)送必須在消息的接收之前获洲。
  • 消息傳遞的方式,多線程之間沒有公共的狀態(tài)(變量)殿如,那么線程之間必須通過明確的傳遞狀態(tài)(變量)來顯式進行通信贡珊。在此模式下,同步實現(xiàn)是顯式進行的涉馁,必須顯式指定某個方法或某段代碼需要在線程之間互斥執(zhí)行门岔。

Java中的同步模式是什么?

同步機制是指程序用于控制不同線程之間操作發(fā)生相對順序的機制烤送。

Java生態(tài)中的并發(fā)編程模型采用的是共享內存模型寒随,因此在Java線程之間的通信總是隱式進行, 整個通信過程對開發(fā)者是黑盒的帮坚,如果編寫多線程程序的開發(fā)者不深入理解這種隱式模式下的線程之間通信機制妻往,就會會出現(xiàn)內存可見性和一致性的問題,我們統(tǒng)稱為線程不安全問題试和。

存在內存可見問題

Java應用程序中讯泣, 所有實例域、靜態(tài)域和數(shù)組元素存儲在堆內存中灰署, 堆內存在線程之間共享判帮。會存在這內存可見性問題。

不存在內存可見問題

局部變量(Local variables) 溉箕, 方法定義參數(shù)(java語言規(guī)范稱之為formal method parameters) 和異常處理器參數(shù)(exception handler parameters) 不會在線程之間共享晦墙,它們不會有內存可見性問題,也不受內存模型的影響肴茄。

所以晌畅,我們在開發(fā)多線程場景下的程序的時候主要需要關注的就是內存可見問題變量,包含:實例域寡痰、靜態(tài)域和數(shù)組元素抗楔。

而為了降低并發(fā)編程的難度和門檻棋凳,這些線程之間的數(shù)據(jù)同步和通信控制就交由一個特定的數(shù)據(jù)模型進行控制和管理,我們稱之為Java內存模型(JMM)连躏。

Java內存模型(JMM)

JMM決定在程序運行中剩岳,一個線程對共享變量的寫入何時對另一個線程可見。

JMM定義了線程和主內存之間的抽象關系

線程之間的共享變量存儲在主內存中入热,每個線程都有一個私有的本地內存 拍棕, 本地內存中存儲了該線程以讀/寫共享變量的副本。

本地內存是JMM的一個抽象概念勺良, 并不真實存在绰播。它涵蓋了緩存, 寫緩沖區(qū)尚困, 寄存器以及其他的硬件和編譯器優(yōu)化蠢箩。

Java 內存模型的抽象示意圖如下:

[圖片上傳失敗...(image-4668d3-1679827701944)]

由上圖可見,線程A與線程B之間如要數(shù)據(jù)通信事甜,需要有以下兩個步驟:

  1. 線程A把本地內存A中更新過的共享變量刷新到主內存中去谬泌。
  2. 線程B到主內存中去讀取線程A之前已更新過的共享變量

下面通過示意圖來說明這兩個步驟:

[圖片上傳失敗...(image-c991b9-1679827701944)]

如上圖所示讳侨,本地內存A和B有主內存中共享變量x的副本呵萨。假設初始時,這三個內存中的x值都為0跨跨。

  1. 線程A在執(zhí)行時潮峦,把更新后的x值,臨時存放在自己的本地內存A中勇婴。
  2. 線程A和線程B需要通信時忱嘹,線程A首先會把自己本地內存中修改后的x值刷新到主內存中,此時主內存中的x值變了耕渴。
  3. 線程B到主內存中去讀取線程A更新后的x值拘悦,此時線程B的本地內存的x值也變了。

總結一下就是橱脸,這兩個步驟數(shù)據(jù)角度而言是線程A在向線程B發(fā)送消息础米,而且這個通信過程必須要經過主內存。JMM通過控制主內存與每個線程的本地內存之間的交互添诉, 來為程序提供內存可見性保證屁桑。

線程不安全因素之一(指令重排序問題)

基于上述所說的場景之下,JVM為了在執(zhí)行程序時為了提高性能栏赴,編譯器和處理器常常會對指令做重排序蘑斧。在此我們將按照重排序的執(zhí)行時間前后分為重排序分三種類型,如下圖所示。

[圖片上傳失敗...(image-ad7254-1679827701944)]

  • 第一步屬于編譯器重排序:編譯器優(yōu)化的重排序竖瘾,編譯器在不改變單線程程序語義的前提下沟突,可以重新安排語句的執(zhí)行順序。

  • 第二步屬于處理器重排序:指令級并行的重排序捕传,現(xiàn)代處理器采用了指令級并行技術(Instruction-Level Parallelism惠拭, ILP) 來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性庸论, 處理器可以改變語句對應機器指令的執(zhí)行順序求橄。

  • 第三步屬于處理器重排序:內存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū)葡公,這使得加載和存儲操作看上去可能是在亂序執(zhí)行,此處特別是針對與本地內存和共享主存之間的更新操作的一致性和可見性

這些重排序都可能會導致多線程程序出現(xiàn)內存可見性問題条霜。

JMM解決重排序的線程不安全問題

解決編譯器級別重排序

  • JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序催什,此處注意:不是所有的編譯器重排序都要禁止

解決處理器級別重排序

  • JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列時宰睡, 插入特定類型的內存屏障(memory barriers蒲凶, 也可以稱之為memory fence)指令, 通過 內存屏障 指令來禁止特定類型的處理器重排序拆内,此處注意:不是所有的處理器重排序都要禁止)旋圆。

總結一下,針對于JMM屬于語言級的內存模型麸恍, 它確保在不同的編譯器和不同的處理器平臺之上灵巧,通過禁止特定類型的編譯器重排序和處理器重排序,從而實現(xiàn)了內存的可見性以及一致性抹沪。

處理器重排序與內存屏障指令

上面說了其實是通過插入了內存屏障指令刻肄,從而控制住了對應的處理器級別的指令重排。

線程不安全因素之一(寫緩存處理模式)

  • 現(xiàn)代的處理器使用寫緩沖區(qū)來臨時保存向內存寫入的數(shù)據(jù)融欧,寫緩沖區(qū)可以保證指令流水線持續(xù)運行敏弃,它可以避免由于處理器停頓下來等待向內存寫入數(shù)據(jù)而產生的延遲。

  • 通過以批處理的方式刷新寫緩沖區(qū)噪馏,以及合并寫緩沖區(qū)中對同一內存地址的多次寫麦到,可以減少對內存總線的占用。雖然寫緩沖區(qū)有這么多好處欠肾,但每個處理器上的寫緩沖區(qū)瓶颠,僅僅對它所在的處理器可見。

這個特性會對內存操作的執(zhí)行順序產生重要的影響董济,處理器對內存的讀/寫操作的執(zhí)行順序步清,不一定與內存實際發(fā)生的讀/寫操作順序一致。

[圖片上傳失敗...(image-db09cd-1679827701944)]

  1. 處理器A處理器B可以同時把共享變量寫入自己的寫緩沖區(qū)(A1,B1)
  2. 從內存中讀取另一個共享變量(A2廓啊,B2)
  3. 最后才把自己寫緩存區(qū)中保存的臟數(shù)據(jù)刷新到內存中(A3欢搜,B3)。

從內存操作實際發(fā)生的順序來看谴轮,直到處理器A執(zhí)行A3來刷新自己的寫緩存區(qū)炒瘟,寫操作A1才算真正執(zhí)行了。雖然處理器A執(zhí)行內存操作的順序為:A1->A2第步,但內存操作實際發(fā)生的順序卻是:A2->A1疮装。此時,處理器A的內存操作順序被重排序了(處理器B的情況和處理器A一樣)粘都。

由于現(xiàn)代的處理器都會使用寫緩沖區(qū)廓推,因此現(xiàn)代的處理器都會允許對寫-讀操作重排序。常見的處理器都允許Store-Load重排序翩隧,常見的處理器都不允許對存在數(shù)據(jù)依賴的操作做重排序樊展。

內存屏障指令

為了保證內存可見性, java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序堆生。JMM把內存屏障指令分為下列四類:

內存屏障類型 指令示例 備注
LoadLoad Barries Load1\LoadLoad\Load2 確保Load1數(shù)據(jù)的裝載专缠,之前于Load2及所有后續(xù)裝載指令的裝載
StoreStore Barries Store1\StoreStore\Store2 確保Store1數(shù)據(jù)對其他處理器可見(刷新到內存),之前于Store2及所有后續(xù)存儲指令的存儲淑仆。
LoadStore Barriers Load1\ LoadStore\Store2 確保Load1數(shù)據(jù)裝載涝婉, 之前于Store2及所有后續(xù)的存儲指令刷新到內存
StoreLoad Barriers Store1\StoreLoad\Load2 確保Storel數(shù)據(jù)對其他處理器變得可見(指刷新到內存),之前于Load2及所有后續(xù)裝載指令的裝載蔗怠。StoreLoad Barriers會使該屏障之前的所有內存訪問指令(存儲和裝載指令)完成之后墩弯,才執(zhí)行該屏障之后的內存訪問指令。

**StoreLoad Barriers是一個“全能型”的屏障蟀淮, 它同時具有其他三個屏障的效果∽钭。現(xiàn)代的多處理器大都支持該屏障(其他類型的屏障不一定被所有處理器支持)。執(zhí)行該屏障開銷會很昂貴怠惶,因為當前處理器通常要把寫緩沖區(qū)中的數(shù)據(jù)全部刷新到內存中(buffer fully flush) **涨缚。

未完善待續(xù)!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末策治,一起剝皮案震驚了整個濱河市脓魏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌通惫,老刑警劉巖茂翔,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異履腋,居然都是意外死亡珊燎,警方通過查閱死者的電腦和手機惭嚣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悔政,“玉大人晚吞,你說我怎么就攤上這事∧惫” “怎么了槽地?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芦瘾。 經常有香客問我捌蚊,道長,這世上最難降的妖魔是什么近弟? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任缅糟,我火速辦了婚禮,結果婚禮上祷愉,老公的妹妹穿的比我還像新娘溺拱。我一直安慰自己,他們只是感情好谣辞,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沐扳,像睡著了一般泥从。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沪摄,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天躯嫉,我揣著相機與錄音,去河邊找鬼杨拐。 笑死祈餐,一個胖子當著我的面吹牛,可吹牛的內容都是我干的哄陶。 我是一名探鬼主播帆阳,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屋吨!你這毒婦竟也來了蜒谤?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤至扰,失蹤者是張志新(化名)和其女友劉穎鳍徽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敢课,經...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡阶祭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年绷杜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片濒募。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡鞭盟,死狀恐怖,靈堂內的尸體忽然破棺而出萨咳,到底是詐尸還是另有隱情懊缺,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布培他,位于F島的核電站鹃两,受9級特大地震影響,放射性物質發(fā)生泄漏舀凛。R本人自食惡果不足惜俊扳,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望猛遍。 院中可真熱鬧馋记,春花似錦、人聲如沸懊烤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腌紧。三九已至茸习,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間壁肋,已是汗流浹背号胚。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浸遗,地道東北人猫胁。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像跛锌,于是被迫代替她去往敵國和親弃秆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內容