Java并發(fā)編程:JMM (Java內存模型) 以及與volatile關鍵字詳解

計算機系統(tǒng)的一致性

在現代計算機操作系統(tǒng)中舟茶,多任務處理幾乎是一項必備的功能馁蒂,因為嵌入了多核處理器鲤孵,計算機系統(tǒng)真正做到了同一時間執(zhí)行若干個任務用僧,是名副其實的多核系統(tǒng)结胀。在多核系統(tǒng)中,為了提升CPU與內存的交互效率责循,一般都設置了一層 “高速緩存區(qū)” 作為內存與處理器之間的緩沖糟港,使得CPU在運算的過程中直接從高速緩存區(qū)讀取數據,一定程度上解決了性能的問題院仿。但是着逐,這樣也帶來了一個新問題,就是“緩存一致性”的問題意蛀。比如耸别,多核的情況下,每個處理器都有自己的緩存區(qū)县钥,數據如何保持一致性秀姐。針對這個問題,現代的計算機系統(tǒng)引入多處理器的數據一致性的協(xié)議,包括MOSI坑质、Synapse账磺、Firely、DragonProtocol等蠢沿。

當處理器通過高速緩存區(qū)與主內存發(fā)生交互時,對數據的讀寫必須遵循協(xié)議規(guī)定的標準匾效,用一張關系圖表示的話大概如下:

而Java的內存模型 (JMM) 可以說與硬件的一致性模型很相似舷蟀,采用的是共享內存的線程通信機制。Java內存模型

Java內存模型規(guī)定了所有的變量都存儲在主內存中面哼,每個線程擁有自己的工作內存野宜,工作內存中保存了被該線程使用的變量的主內存副本拷貝,線程只能操作自己工作內存的變量副本魔策,操作完變量后會更新到主內存匈子,通過主內存來完成與其他線程間變量值的傳遞。此模型的交互關系如下圖所示:

然而闯袒,Java的內存模型只是反映了虛擬機內部的線程處理機制虎敦,并不保證程序本身的并發(fā)安全性游岳。舉一個例子,在程序中對一個共享變量做自增操作:

i++其徙;

假設初始化的時候i=0吭历,當跑到此程序時,線程首先從主內存讀取i的值擂橘,然后復制到自己的工作內存晌区,進行i++操作,最后將操作后的結果從工作內存復制到主內存中通贞。如果是兩個線程執(zhí)行i++的程序朗若,預期的結果是2。但真的是這樣嗎昌罩?答案是否定的哭懈。

假設線程1讀取主內存的i=0,復制到自己的工作內存茎用,在進行i++的操作后還沒來得及更新到主內存遣总,這時線程2也讀取i=0,做了同樣的操作轨功,那么最終得到的結果為1旭斥,而不是2。

這是典型的關于多線程并發(fā)安全例子古涧,也是Java并發(fā)編程中最值得探討的話題之一垂券,一般來說,處理這種問題有兩種手段:

加鎖羡滑,比如同步代碼塊的方式菇爪。保證同一時間只能有一個線程能執(zhí)行i++這條程序。

利用線程間的通信柒昏,比如使用對象的wait和notify方法來凳宙。

因為本文主要是探究 JMM 和 volatile 關鍵字的知識,具體怎么實現并發(fā)處理就不做深入探討了职祷,改天看看抽個時間再寫篇博文專門講解好了氏涩。

內存模型的3個重要特征

初步了解完什么是JMM后,我們來進一步了解它的重要特征堪旧。值得說明的是削葱,在Java多線程開發(fā)中,遵循著三個基本特性淳梦,分別是原子性、可見性和有序性昔字,而Java的內存模型正是圍繞著在并發(fā)過程中如何處理這三個特征建立的爆袍。

原子性

原子性是指操作是原子性的首繁,不可中斷的。舉個例子:

String s="abc";

這個操作是直接賦值陨囊,是原子性操作弦疮。而類似下面這段代碼就不是原子性了:

i++;當執(zhí)行i++時,需要先獲取i的值蜘醋,然后再執(zhí)行i+1胁塞,相當于包含了兩個操作,所以不是原子性压语。

可見性

可見性是指共享數據的時候啸罢,一個線程修改了數據,其他線程知道數據被修改胎食,會重新讀取最新的主存的數據扰才。就像前面說的兩個線程處理i++的問題,線程1改完后沒有更新到主內存厕怜,所以線程2是不知道的衩匣。

有序性

是指代碼執(zhí)行的有序性,對于一個線程執(zhí)行的代碼粥航,我們可以認為代碼是依次執(zhí)行的琅捏,但并發(fā)中可能就會出現亂序,因為代碼有可能發(fā)生指令重排序(Instruction Reorder)递雀,重排后的指令與原指令的順序未必一致午绳。

指令重排序

編譯器能夠自由的以優(yōu)化的名義去改變指令順序。在特定的環(huán)境下映之,處理器可能會次序顛倒的執(zhí)行指令拦焚。是為指令的重排序,尤其是并發(fā)的情況下杠输。

java提供了volatile和synchronized來保證線程之間操作的有序性赎败。volatile含有禁止指令重排序的語義(即它的第二個語義),synchronized規(guī)定一個變量在同一時刻只允許一條線程對其lock操作蠢甲,也就是說同一個鎖的兩個同步塊只能串行進入僵刮。禁止了指令的重排序。

volatile關鍵字

說到了volatile鹦牛,我們就有必要了解一下這個關鍵字是做什么的搞糕。

準確來說,volatile是java提供的輕量的同步機制曼追。它有兩個特性:

保證修飾的變量對所有線程的可見性窍仰。

禁止指令的重排序優(yōu)化。

保證可見性和防止指令重排

簡單寫段代碼說明一下:

publicclassVolatileDemo{privatestaticbooleanisReady;privatestaticintnumber;privatestaticclassReaderThreadextendsThread{@Overridepublicvoidrun(){while(!isReady); System.out.println("number = "+number); } }publicstaticvoidmain(String[] args){newReaderThread().start();try{ Thread.sleep(1000); number =42; isReady =true; Thread.sleep(1000); }catch(InterruptedException e) { e.printStackTrace(); } }}

在上面的代碼中礼殊,ReaderThread只有在isReady 為 true 時才會打印出 number 的值驹吮,然而针史,真實的情況有可能是打印不出來(可能性比較小,但還是有)碟狞,因為線程ReaderThread線程無法看到主線程中對isReady的修改啄枕,導致while循環(huán)永遠無法退出,同時族沃,因為有可能發(fā)生指令重排频祝,導致下面的代碼不能按順序執(zhí)行:

number=42;isReady=true;

也就是能打印的話,number值可能是0脆淹,不是42常空。如果在變量加上volatile關鍵字,告訴Java虛擬機這兩個變量可能會被不同的線程修改未辆,那么就可以防止上述兩種不正常的情況的發(fā)生窟绷。

不能保證原子性

volatile能保證可見性和有序性,但無法保證原子性咐柜,比如下面的例子:

publicclassVolatileDemo{publicstaticvolatileinti =0;publicstaticvoidincrease(){ i++; }publicstaticvoidmain(String[] args) throws InterruptedException{ VolatileDemo test =newVolatileDemo();for(inti =0; i <10; i++) {newThread(() -> {for(intj =0; j <1000; j++) test.increase(); }).start(); }? Thread.sleep(1000); System.out.println(test.i); }}

正常情況下兼蜈,我們期望上面的main函數執(zhí)行完后輸出的結果是10000,但你會發(fā)現拙友,結果總是會小于10000为狸,因為increase()方法中的i++操作不是原子性的,分成了讀和寫兩個操作遗契。假設當線程1讀取了 i 的值辐棒,還沒有修改,線程2這時也進行了讀取牍蜂。然后漾根,線程1修改完了,通知線程2重新讀取 i 的值鲫竞,可這時它不需要讀取 i辐怕,它仍執(zhí)行寫操作,然后賦值給主線程从绘,這時數據就會出現問題寄疏。

所以,一般針對共享變量的讀寫操作僵井,還是需要用鎖來保證結果陕截,例如加上 synchronized關鍵字。

在這里我為java的同道者準備了以上相關資料批什,有需要的可以加群:810589193农曲,點擊鏈接加入群聊【Java架構學習交流群】:https://jq.qq.com/?_wv=1027&k=5deQUBl里面有阿里Java高級大牛直播講解知識點,分享知識渊季,課程內容都是各位老師多年工作經驗的梳理和總結朋蔫,帶著大家全面罚渐、科學地建立自己的技術體系和技術認知却汉!

開發(fā)人員需能夠自我激勵驯妄,主動學習新技術,并在職業(yè)生涯中給自己扣上很多帽子合砂。 繼而不斷挑戰(zhàn)自我青扔,然后更好地解決問題,這就是編程的本質翩伪。 知識很重要微猖,在某些復雜問題的情況下更是如此。在變化如此之快的IT技術領域中缘屹,知識的獲取在任何時候比我們已會的技能更為重要凛剥。

可以加群:810589193,點擊鏈接加入群聊【Java架構學習交流群】:https://jq.qq.com/?_wv=1027&k=5deQUBl我會分享一些免費的資深架構師錄制的視頻錄像:有Spring轻姿,MyBatis犁珠,Netty源碼分析,高并發(fā)互亮、高性能犁享、分布式、微服務架構的原理豹休,JVM性能優(yōu)化這些成為架構師必備的知識體系炊昆。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市威根,隨后出現的幾起案子凤巨,更是在濱河造成了極大的恐慌,老刑警劉巖洛搀,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敢茁,死亡現場離奇詭異,居然都是意外死亡姥卢,警方通過查閱死者的電腦和手機卷要,發(fā)現死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來独榴,“玉大人僧叉,你說我怎么就攤上這事」桌疲” “怎么了瓶堕?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長症歇。 經常有香客問我郎笆,道長谭梗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任宛蚓,我火速辦了婚禮激捏,結果婚禮上,老公的妹妹穿的比我還像新娘凄吏。我一直安慰自己远舅,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布痕钢。 她就那樣靜靜地躺著图柏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪任连。 梳的紋絲不亂的頭發(fā)上蚤吹,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音随抠,去河邊找鬼裁着。 笑死,一個胖子當著我的面吹牛暮刃,可吹牛的內容都是我干的跨算。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼椭懊,長吁一口氣:“原來是場噩夢啊……” “哼诸蚕!你這毒婦竟也來了?” 一聲冷哼從身側響起氧猬,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤背犯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盅抚,有當地人在樹林里發(fā)現了一具尸體漠魏,經...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年妄均,在試婚紗的時候發(fā)現自己被綠了柱锹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡丰包,死狀恐怖禁熏,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情邑彪,我是刑警寧澤瞧毙,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響宙彪,放射性物質發(fā)生泄漏矩动。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一释漆、第九天 我趴在偏房一處隱蔽的房頂上張望悲没。 院中可真熱鬧,春花似錦灵汪、人聲如沸檀训。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渗鬼,卻和暖如春览露,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背譬胎。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工差牛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堰乔。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓偏化,卻偏偏與公主長得像,于是被迫代替她去往敵國和親镐侯。 傳聞我的和親對象是個殘疾皇子侦讨,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內容