詳解Java內存模型

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文章已同步至GitHub開源項目: ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/shaoxiongdu/JVMStudy","title":null,"type":null},"content":[{"type":"text","text":"JVM底層原理解析","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Java內存模型","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​ JVM虛擬機規範中曾經試圖定義一種Java內存模型,來屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺下都可以達到一致性的內存訪問效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​ 然而定義這樣一套內存模型並非很容易,這個模型必須足夠嚴謹,才能讓Java的併發內存訪問操作不會有歧義。但是也必須足夠寬鬆,這樣使得虛擬機的具體實現能夠有自由的發揮空間來利用各種硬件的優勢。經過長時間的驗證和彌補,到了JDK1.5(實現了JSR133規範)之後,Java內存模型才終於成熟起來了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"主內存和工作內存","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​ Java內存模型規定了所有的變量都存儲在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"主內存","attrs":{}}],"attrs":{}},{"type":"text","text":"(Main Memory)中,每條線程都有自己的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"工作內存","attrs":{}}],"attrs":{}},{"type":"text","text":"(Work Memory)","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作內存中保存了被該線程使用的變量的主內存副本,","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程對變量的讀寫操作必須在工作內存中進行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而不能直接訪問主內存的數據。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同的線程也不能互相讀寫對方的工作內存,線程之間的變量傳遞必須通過主內存傳遞。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"主內存和工作內存的交互","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Java內存模型定義瞭如下八種操作(每一種操作都是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"原子的","attrs":{}}],"attrs":{}},{"type":"text","text":", ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"不可再分","attrs":{}}],"attrs":{}},{"type":"text","text":"的)","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"lock鎖定","attrs":{}}],"attrs":{}},{"type":"text","text":": 作用於主內存,將一個變量標識爲線程獨佔狀態","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"unlock:解鎖","attrs":{}}],"attrs":{}},{"type":"text","text":" : 作用於主內存,將一個線程獨佔狀態的變量釋放","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"read讀取","attrs":{}}],"attrs":{}},{"type":"text","text":" : 從主內存讀取數據到工作內存,便於之後的load操作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"load載入","attrs":{}}],"attrs":{}},{"type":"text","text":": 把read讀取操作從主內存中得到的變量放入工作內存的變量副本中","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"use使用","attrs":{}}],"attrs":{}},{"type":"text","text":": 將工作內存中的變量傳遞給執行引擎 當虛擬機遇到一個需要使用變量值的字節碼時,執行此操作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"assign賦值","attrs":{}}],"attrs":{}},{"type":"text","text":": 將執行引擎中的值賦給工作內存的變量。 當虛擬機遇到一個賦值操作時,執行此操作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"store存儲","attrs":{}}],"attrs":{}},{"type":"text","text":": 將工作內存的值傳遞到主內存 ,便於之後的write操作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"write寫入","attrs":{}}],"attrs":{}},{"type":"text","text":":將store存儲操作中從工作內存中獲取的變量寫入到主內存中","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"舉例:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果要把一個變量從主內存拷貝到工作內存,則依次執行read讀取操作, load載入操作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果要把一個變量從工作內存寫入到主內存,則依次執行store存儲操作,write寫入操作","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"上述的8種操作必須滿足以下規則:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不允許read和load、store和write操作之一單獨出現。也就是說不允許一個變量從主內存讀取但是工作內存不接受,也不允許工作內存發起回寫請求但是主內存不接受。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不允許一個線程丟棄它的最近assign的操作,即變量在工作內存中改變了之後必須同步到主內存中。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個變量在同一時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重複執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量纔會被解鎖。lock和unlock必須成對出現","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行load或assign操作初始化變量的值","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果一個變量事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對一個變量執行unlock操作之前,必須先把此變量同步到主內存中(執行store和write操作)。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"volatile特殊規則","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​ volatile可以說是Java虛擬機提供的最輕量級的同步機制。但是它並不容易被正確,完整的理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​ Java內存模型中規定","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​ 當一個變量被定義爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile","attrs":{}}],"attrs":{}},{"type":"text","text":"之後,表示着線程工作內存無效,對此值的讀寫操作都會直接作用在主內存上,","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此它具備對所有線程的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"立即可見性","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"保證此變量對所有線程的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"立即可見性","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"​ 當變量的值被修改之後,新值對於其他線程是立即可知的。普通變量並不能做到這一點,因爲普通變量的值在線程之間的傳遞是要進過主內存來完成的。比如當線程A對變量進行了回寫操作,線程B只有在A回寫完成之後,在對主內存操作,新值纔對B是可見的。在A回寫到主內存的過程中,B讀取的依舊是舊值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"​ 但是這並不可以推導出 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"基於volatile變量的運算在併發下是安全的","attrs":{}}],"attrs":{}},{"type":"text","text":",因爲在Java中的運算操作符並不是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"原子性","attrs":{}}],"attrs":{}},{"type":"text","text":"的。這導致了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile變量在併發下運算是不安全","attrs":{}}],"attrs":{}},{"type":"text","text":"的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"通過代碼驗證 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile變量在併發下運算是不安全","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們創建20個線程,每個線程對volatile變量進行1000次的自增操作。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n\n* @作者: 寫Bug的小杜 【[email protected]】\n\n* @時間: 2021/07/31\n\n* @描述: 通過代碼驗證 【volatile變量在併發下運算是不安全】\n\n*/\n\npublic class VolatileTest {\n\n //volatile修飾的count\n\n private static volatile int count = 0;\n\n //count自增方法\n\n public static void increment(){\n\n count++;\n\n }\n\n public static void main(String[] args) { //對count進行遞增1000次操作的可運行接口\n\n Runnable runnable = new Runnable() {\n\n @Override\n\n public void run() {\n\n System.out.println(Thread.currentThread().getName() + \"線程開始對count進行遞增操作\");\n\n for (int i = 0; i < 1000; i++) {\n\n increment();\n\n }\n\n System.out.println(Thread.currentThread().getName() + \"線程對count遞增操作結束\");\n\n }\n\n };\n\n // 創建20個線程並啓動\n\n for (int i = 0; i < 20; i++) {\n\n Thread thread = new Thread(runnable);\n\n thread.setName((i+1) + \"號線程\");\n\n thread.start();\n\n }\n\n while (Thread.activeCount() > 2){\n\n //主線程回到就緒狀態\n\n Thread.yield();\n\n }\n\n System.out.println(\"所有線程結束,count = \" + count);\n\n }\n\n}","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"如果此程序在併發下是安全的,那麼count的值最後肯定是20*1000 = 20000;也就是說,如果運行結果爲20000,那麼 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile變量在併發下運算是安全的","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"通過多次運行程序,我們發現,count的值永遠比20000小。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,這是爲什麼呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"我們將上方的代碼進行反編譯,然後分析increment方法的字節碼指令。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"codeinline","attrs":{}}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"0 getstatic #2 \n\n3 iconst_1\n\n4 iadd\n\n5 putstatic #2 \n\n8 return","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以發現,一行count++代碼被分爲 4行字節碼文件去執行。通過對字節碼的分析,我們發現,","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"當偏移量爲0的字節碼getStatic將count的值從局部變量表取到操作數棧頂的時候, ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile","attrs":{}}],"attrs":{}},{"type":"text","text":"保證了此時count的值是正確的,但是在執行iconst_1, iadd這些操作的時候,其他線程已經把count的值改變了,此時,操作數棧頂的count爲過期的數據,所以putStatic字節碼指令就有可能將較小的值同步到主內存中。因此最終的值會比20000稍微小。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說, ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"volatile變量在併發下運算是不安全的","attrs":{}}],"attrs":{}},{"type":"text","text":" 。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"在併發環境下,volatile的變量只是對全部線程即時可見的,如果要進行寫的操作,還是要通過加鎖來解決。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"​","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文章已同步至GitHub開源項目: ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/shaoxiongdu/JVMStudy","title":null,"type":null},"content":[{"type":"text","text":"JVM底層原理解析","attrs":{}}]}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章