JVM:Java記憶體模型(JMM)
Java 記憶體模型
JMM:Java Memory Model
與記憶體結構(執行時資料區等)是不同的兩個概念
JMM 用於遮蔽各種硬體和作業系統的記憶體訪問差異,讓 Java 程式在各平臺能達到一致的記憶體訪問效果。
- 讓 Java 的併發記憶體訪問操作不會產生歧義。
- JVM 能自由利用硬體的特性,獲取更好的執行速度。
主記憶體與工作記憶體
JMM 定義程式中各種變數的訪問規則
這裡的變數不包括區域性變數和方法引數。
主記憶體:所有變數都儲存在主記憶體。
工作記憶體:每個執行緒有自己的工作記憶體,其中儲存了該執行緒所用變數的主記憶體副本。
-
執行緒對變數的操作(讀取、賦值)只在工作記憶體中進行,不能直接讀寫主記憶體中的資料。
-
執行緒之間也無法直接訪問對方工作記憶體的變數,需要通過主記憶體來傳遞。
說明:主記憶體與工作記憶體的概念,與執行時資料區中的堆疊等結構,是不同層次的概念。
兩個概念之間基本沒有任何關係,如果一定要勉強對應
- 主記憶體:對應堆中的物件例項資料部分
- 工作記憶體:對應虛擬機器棧的部分割槽域
三大特性
原子性(Atomicity)、可見性(Visibility)、有序性(Ordering)
JMM 是圍繞著併發過程中這三個特性來建立的。
原子性
舉個例子
兩個執行緒,對一個初值為 0 的靜態變數,分別作自增和自減,結果是 0 嗎?
分析位元組碼
-
getstatic:獲取靜態變數 i 的值
-
iconst_1:從常量池載入一個常量 1
-
iadd / isub:加法/減法
-
putstatic:將修改後的值,賦值給靜態變數 i
// i++ 對應位元組碼 getstatic i iconst_1 iadd putstatic i // i-- 對應位元組碼 getstatic i iconst_1 isub putstatic i
結果可能是正數、負數、0。
在多執行緒情況下,對靜態變數 i 的操作不是原子性。
假如 8 行位元組碼交錯執行,就可能出現結果非 0 的情況。
解決方法
同步關鍵字:synchronized
synchronized(Object) { // 要求原子性操作的程式碼 }
可見性
舉個例子:退不出的迴圈
執行緒 t 由於 run == true 進入迴圈。
主執行緒將 run 設為 false,但執行緒 t 沒有退出迴圈。
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false;
}
淺分析一下
- 執行緒 t 開始時,從主記憶體中將 run 的值讀到工作記憶體。
- 由於執行緒對變數的操作只在工作記憶體中進行,不能直接讀寫主記憶體中的資料。
- 主記憶體中的資料改變,而執行緒 t 只是讀取本地記憶體中的 run,沒有從主記憶體中再次讀取 run 值
解決方法
易變關鍵字:volatile
- 修飾成員變數和靜態成員變數
- 要求執行緒從主記憶體中讀取變數值,而不是從工作快取。
注意:synchronized 也可以保證原子性,但效率低。
有序性
舉個例子:詭異的結果
兩個執行緒分別執行以下兩個方法,最終 num 和 ready 的值是什麼?
int result = 0;
int num = 1;
boolean ready = false;
// 執行緒t1執行
public void m1() {
if(ready) {
// ①
result = num + num;
} else {
// ②
result = 1;
}
}
// 執行緒t2執行
public void m2() {
// ③
num = 2;
// ④
ready = true;
}
一共有以下三種情況:結果可能是 1 或 4。
-
先進入 m1():條件假,進入 else 執行 ②
-
先進入 m2():執行③,④還未執行;此時進入 m1():條件假,執行②
-
先進入 m2():執行③④;再進入 m1():條件真,執行①
還有一種情況:result == 2
這是什麼情況呢?先來看看圖示。
-
先進入 m2():執行④,③未執行
-
此時進入 m1():條件真,執行①
指令重排:執行期優化的策略,也就是以上的現象。
簡單來說,JVM 會將容易執行的程式碼優先執行。
而程式碼順序的改變,導致了結果的改變。
解決方法
volatile:可以禁用指令重排。
經典應用:單例模式——雙重檢查加鎖(double-checked locking)