輕鬆學JVM(二)——內存模型、可見性、指令重排序
上一篇我們介紹了JVM的基本運行流程以及內存結構,對JVM有了初步的認識,這篇文章我們將根據JVM的內存模型探索java當中變數的可見性以及不同的java指令在並發時可能發生的指令重排序的情況。
內存模型
首先我們思考一下一個java線程要向另外一個線程進行通信,應該怎麼做,我們再把需求明確一點,一個java線程對一個變數的更新怎麼通知到另外一個線程呢?我們知道java當中的實例對象、數組元素都放在java堆中,java堆是線程共享的。(我們這裡把java堆稱為主內存),而每一個線程都是自己私有的內存空間(稱為工作內存),如果線程1要向線程2通信,一定會經過類似的流程:
1、 線程1將自己工作內存中的X更新為1並刷新到主內存中;
2、 線程2從主內存讀取變數X=1,更新到自己的工作內存中,從而線程2讀取的X就是線程1更新後的值。
從上面的流程看出線程之間的通信都需要經過主內存,而主內存與工作內存的交互,則需要Java內存模型(JMM)來管理器。下圖演示了JMM如何管理主內存和工作內存:
當線程1需要將一個更新後的變數值刷新到主內存中時,需要經過兩個步驟:
1、 工作內存執行store操作;
2、 主內存執行write操作;
完成這兩步即可將工作內存中的變數值刷新到主內存,即線程1工作內存和主內存的變數值保持一致;
當線程2需要從主內存中讀取變數的最新值時,同樣需要經過兩個步驟:
1、主內存執行read操作,將變數值從主內存中讀取出來;
2、工作內存執行load操作,將讀取出來的變數值更新到本地內存的副本;
完成這兩步,線程2的變數和主內存的變數值就保持一致了。
可見性
Java中有一個關鍵字volatile,它有什麼用呢?這個答案其實就在上述java線程間通信機制中,我們想像一下,由於工作內存這個中間層的出現,線程1和線程2必然存在延遲的問題,例如線程1在工作內存中更新了變數,但還沒刷新到主內存,而此時線程2獲取到的變數值就是未更新的變數值,又或者線程1成功將變數更新到主內存,但線程2依然使用自己工作內存中的變數值,同樣會出問題。不管出現哪種情況都可能導致線程間的通信不能達到預期的目的。例如以下例子:
//線程1 boolean stop = false; while(!stop){ doSomething; } //線程2
stop
= true;
這個經典的例子表示線程2通過修改stop的值,控制線程1中斷,但在真實環境中可能會出現意想不到的結果,線程2在執行之後,線程1並沒有立刻中斷甚至一直不會中斷。出現這種現象的原因就是線程2對線程1的變數更新無法第一時間獲取到。
但這一切等到Volatile出現後,再也不是問題,Volatile保證兩件事:
1、 線程1工作內存中的變數更新會強制立即寫入到主內存;
2、 線程2工作內存中的變數會強制立即失效,這使得線程2必須去主內存中獲取最新的變數值。
所以這就理解了Volatile保證了變數的可見性,因為線程1對變數的修改能第一時間讓線程2可見。
指令重排序
關於指令排序我們先看一段代碼:
int a = 0; boolean flag = false;
//線程1
public void writer {
a = 1;
flag = true;
}
//線程2
public void reader {
if (flag) {
int i= a+1;
...... }
}
線程1依次執行a=1,flag=true;線程2判斷到flag==true後,設置i=a+1,根據代碼語義,我們可能會推斷此時i的值等於2,因為線程2在判斷flag==true時,線程1已經執行了a=1;所以i的值等於a+1=1+1=2;但真實情況卻不一定如此,引起這個問題的原因是線程1內部的兩條語句a=1;flag=true;可能被重新排序執行,如圖:
這就是指令重排序的簡單演示,兩個賦值語句儘管他們的代碼順序是一前一後,但真正執行時卻不一定按照代碼順序執行。你可能會說,有這個指令重排序那不是亂套了嗎?我寫的程序都不按我的代碼流程走,這怎麼玩?這個你可以放心,你的程序不會亂套,因為java和CPU、內存之間都有一套嚴格的指令重排序規則,哪些可以重排,哪些不能重排都有規矩的。下列流程演示了一個java程序從編譯到執行會經歷哪些重排序:
在這個流程中第一步屬於編譯器重排查,編譯器重排序會按JMM的規範嚴格進行,換言之編譯器重排序一般不會對程序的正確邏輯造成影響。第二、三步屬於處理器重排序,處理器重排序JMM就不好管了,怎麼辦呢?它會要求java編譯器在生成指令時加入內存屏障,內存屏障是什麼?你可以理解為一個不透風的保護罩,把不能重排序的java指令保護起來,那麼處理器在遇到內存屏障保護的指令時就不會對它進行重排序了。關於在哪些地方該加入內存屏障,內存屏障有哪些種類,各有什麼作用,這些知識點這裡就不再闡述了。可以參考JVM規範相關資料。
下面介紹一下在同一個線程中,不會被重排序的邏輯:
這三種情況中,任意改變一個代碼的順序,結果都會大不相同,對於這樣的邏輯代碼,是不會被重排序的。注意這是指單線程中不會被重排序,如果在多線程環境下,還是會產生邏輯問題,例如我們一開始舉的例子。
結語
本文簡單介紹了java在實現線程間通信時的簡單原理,並介紹了volatile關鍵字的作用,最後介紹了java當中可能會出現指令重排序的情況。下一篇將介紹JVM中的參數設置對java程序的影響。
參考資料:
《實戰Java虛擬機》 葛一鳴
《深入理解Java虛擬機(第2版)》 周志明
《深入理解Java內存模型》 程曉明
TAG:達人科技 |
※為什麼SDN和IBN需要更好的網路可見性
※細數StG-44的三大首創,二戰德國槍械設計師的用心程度可見一斑
※StG-44的三大首創,二戰德國槍械設計師的用心程度可見一斑
※透明PVC包包里裝的可是你清晰可見的品味(別裝)
※從太空中可見!NASA最新圖片顯示美國加州致命大火的規模
※全球手機廠商印度三國殺:OPPO在建工廠中可見猴子發獃、孔雀擺拍
※全球手機廠印度三國殺:OPPO在建工廠中可見猴子發獃、孔雀擺拍
※康寧發布可穿戴設備專用大猩猩玻璃DX/DX+:陽光下清晰可見
※Vishay新款高速PIN光電二極體進一步提升可見光靈敏度,為可穿戴設備實現超薄感測器設計
※曝三星Watch Active 2渲染圖:更多具體細節可見
※iPhone 6S換電池前後運行速度實測:肉眼可見的性能削弱
※DOTA2:TI8總獎金正在用肉眼可見速度增長,語音輪播他leile!
※【E.N.】每周一星(2) | ▓▓▓▓刮開可見
※肉眼可見的真電池充電,XTAR VC2S智能充電器體驗
※拆機iPhoneXR可見做工優秀 電池中國製造為啥寫韓文
※王之博 視所可見 藝術匯 ART FRONTIER 展評
※一種新型可以用可見光的3D生物列印水凝膠
※LG G7 ThinQ細節圖曝光!「劉海」四個開孔清晰可見
※三星Galaxy Fold上手:摺痕可見、摺疊後不貼合
※NASA探測器從太陽大氣層內拍攝首張照片 冕流清晰可見