註:本文主要參考自《深入理解Java虛擬機(第二版)》和《深入理解Java記憶體模型》1、Java記憶體模型(JMM)Java記憶體模型的主要目標:定義在虛擬機中將變數存儲到記憶體和從記憶體中取出變數這樣的底層細節。註意:上邊的變數指的是共用變數(實例欄位、靜態欄位、數組對象元素),不包括線程私有變數(局部變...
註:本文主要參考自《深入理解Java虛擬機(第二版)》和《深入理解Java記憶體模型》
1、Java記憶體模型(JMM)
Java記憶體模型的主要目標:定義在虛擬機中將變數存儲到記憶體和從記憶體中取出變數這樣的底層細節。
註意:上邊的變數指的是共用變數(實例欄位、靜態欄位、數組對象元素),不包括線程私有變數(局部變數、方法參數),因為私有變數不會存在競爭關係。
1.1、記憶體模型就是一張圖:
說明:
- 所有共用變數存於主記憶體
- 每一條線程都有自己的工作記憶體(就是上圖所說的本地記憶體)
- 工作記憶體中保存了被該線程使用到的變數的主記憶體副本
註意:
- 線程對變數的操作都要在工作記憶體中進行,不能直接操作主記憶體
- 不同的線程之間無法直接訪問對方的工作記憶體中的變數
- 不同線程之間的變數的傳遞必須通過主記憶體
類比:(註意:主記憶體與工作記憶體只是一個概念,與堆棧記憶體沒有關係,下邊的類比只是幫助理解)
- 主記憶體:對應於Java堆中的對象實例數據部分(註意:堆中還保存了對象的其他信息,eg.Mark Word、Klass Point和用於位元組對其補白的填充數據)
- 工作記憶體:對應於棧中的部分區域
1.2、8條記憶體屏障指令:
下麵只列出6條與之後內容相關的,其餘的查看《深入理解Java虛擬機》
- lock:作用於主記憶體,把一個變數標識為一條線程獨占的狀態
- unlock:作用於主記憶體,把一個處於鎖定的變數解鎖
下邊四條是與volatile實現記憶體可見性直接相關的四條(store、write、read、load)
- store:把工作記憶體中的變數的值傳送到主記憶體中
- write:把store操作從工作記憶體中得到的變數值放入到主記憶體的變數中
- read:把一個變數的值從主記憶體中傳輸到線程的工作記憶體
- load:把read操作從主記憶體中獲取到的變數值放入工作記憶體的變數中去
註意:
- 一個變數在同一時刻只允許一條線程對其進行lock操作
- lock操作會將該變數在所有線程工作記憶體中的變數副本清空,否則就起不到鎖的作用了
- lock操作可被同一條線程多次進行,lock幾次,就要unlock幾次(可重入鎖)
- unlock之前必須先執行store-write
- store-write必須成對出現(工作記憶體-->主記憶體)
- read-load必須成對出現(主記憶體-->工作記憶體)
2、變數對所有線程的可見性
可見性:線程1對共用變數的修改能及時被線程2看到
2.1、共用變數不可見的原因
- 共用變數更新後的值沒有在工作記憶體和主記憶體之間及時更新
- 線程交錯執行
- 指令重排序結合線程交錯執行
2.2、實現共用變數及時更新的措施
線程1修改過共用變數後,將共用變數刷到主記憶體,然後,線程2從主記憶體讀取該共用變數,將該共用變數載入到工作記憶體中
註意:在短時間內的高併發情況下,如果發生下列三種情況,則線程2就讀不到線程1修改過的最新的值了,
- 可能線程1根本來不及將修改過後的共用變數刷到主記憶體(這個時間非常短,但是還是有)的時候,線程2就已經讀取了原有的主記憶體變數到其工作記憶體中。
- 可能線程1雖然將修改過後的值刷到了主記憶體中,但是線程2的工作記憶體中的變數副本還沒來得及從CPU刷新回來,所以線程2讀取到的還是原來的工作記憶體中的變數副本
- 可能線程1根本來不及將修改過後的共用變數刷到主記憶體的時候,同時,線程2的工作記憶體中的變數副本還沒來得及從CPU刷新回來
註意:工作記憶體中的變數副本在使用之後,不會立刻消失掉,會一直存在,這樣其值也一直不變,直到對其進行寫操作或數據從CPU中刷新回來(類比volatile-read的作用)。
2.3、指令重排序:代碼書寫順序與實際執行順序不同(編譯器或處理器為提高程式性能做的優化)
eg.
書寫代碼的順序如下:
1 int a = 12; 2 int b = 13; 3 int c = a+b;View Code
可能實際執行代碼的順序如下:
1 int b = 13; 2 int a = 12; 3 int c = a+b;View Code
總結:本文大概介紹了一下Java記憶體模型以及與共用變數可見性的一些概念,為下邊的volatile做準備。