注:本文主要參考自《深入理解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;
可能實際執行代碼的順序如下:

1 int b = 13; 2 int a = 12; 3 int c = a+b;
總結:本文大概介紹了一下Java內存模型以及與共享變量可見性的一些概念,為下邊的volatile做准備。