1. JMM
- 概述
JMM的全稱是Java Memory Model(Java內存模型),線程間通訊是通過共享內存來實現的,所以也叫共享內存模型,它是多線程和併發編程的基礎。
內存模型描述了程序中各個變量(實例域、靜態域和數組元素)之間的關係,以及在實際計算機系統中將變量存儲到內存和從內存中取出變量這樣的底層細節
通過該內存模型屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果,實現了跨平臺
- 規範
JMM規定了所有的變量都存儲在主內存中。每個線程還有自己的工作內存, 線程的工作內存中保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同的線程之間也無法直接訪問對方工作內存中的變量,線程之間值的傳遞都需要通過主內存來完成。
-
內存原子操作
結合以下代碼進行分析一下內存的8個原子操作
public class JMMTest { private static boolean flag = false; public static void main(String[] args) { new Thread(()->{ while (!flag) { //wait } System.out.println("=======success====="); }, "threadB").start(); //保證線程B先執行 try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ flag = true; }, "threadA").start(); } }
大致過程如下圖所示
對於線程B
1)在程序使用flag變量之前先要進行read
操作,將主內存的共享變量flag傳輸到線程的工作內存
2)將傳輸到工作內存的變量進行load
操作,此時線程B中就flag=false
3)CPU使用工作內存的變量進行use
操作,對flag進行取反運算對於線程A
1)在程序使用flag變量之前先要進行read
操作,將主內存的共享變量flag傳輸到線程的工作內存
2)將傳輸到工作內存的變量進行load
操作,此時線程A工作內存中就flag=false
3)CPU使用工作內存的變量進行use
操作,將flag的值設置爲true
4)將CPU運算的結果賦值給工作內存的變量進行assign
操作,此時線程A工作內存中flag爲true下面我們對代碼稍作修改,將flag的定義加入
volatile
進行修飾private static volatile boolean flag = false;
因爲flag用了volatile修飾,它將給flag帶來以下兩個特性
1)發生assign操作後將工作內存的值立即寫回主內存
2)開起總線的嗅探機制,寫回主內存時將其他工作內存中的該變量置位失效(MESI緩存一致性)此時接着上面介紹內存原子操作繼續講,對於線程A
5)立即將flag的值寫回主內存,此時將先會執行lock
操作,將該內存行進行鎖定,以防其他線程進行髒讀
6)然後執行store
操作,將flag的值傳輸回主內存
7)主內存中執行write
操作,將主內存的flag值置位true
8)最後將鎖釋放,執行unlock
操作對於線程B,由於總線嗅探機制發現flag的值發生改變,線程B工作內存中的flag變量將失效,使用時需要重新從主內存讀取,執行read和load操作。在執行read操作時如果發現該內存地址被鎖住,則需要等到鎖的釋放