上一篇文章帶大家簡單瞭解了對象頭,及mark word的內容,那麼本文將來學習,mark word到底有什麼作用。其實就是synchronized的原理。
先將64位虛擬機的java對象Mark Word放在這,方便後面查看:
|----------------------------------------------------------------------------------------------|
| Mark Word(64bits) | State |
|----------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:0| lock:01 | Nomal |
|----------------------------------------------------------------------------------------------|
| thread:54| epoch:2 |unused:1|age:4|biase_lock:1| lock:01 | Biased |
|----------------------------------------------------------------------------------------------|
| ptr_to_lock_record:62 | lock:00 | Lightweight Locked |
|----------------------------------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:10 | Heavyweight Locked |
|----------------------------------------------------------------------------------------------|
| | lock:11 | Marked for GC |
|----------------------------------------------------------------------------------------------|
一、Monitor
在介紹Mark Word的時候,提到過Monitor,可以稱爲監視器或管程,下面我們看下它到底是個什麼東西。
每個 Java 對象都可以關聯一個 Monitor 對象,如果使用 synchronized 給對象上鎖(重量級)之後,該對象頭的Mark Word 中就被設置指向 Monitor 對象的指針。不加 synchronized 的對象不會關聯Monitor。
首先通過下圖展示管程的組成,以及和java對象的關係:
有一個java對象,上了一把synchronized(重量級鎖),當有線程獲取到鎖時,其對象頭中的Mark Word變成了指向Monitor的指針。(原本mark word當中的內容會存儲到Monitor當中,釋放時會取出這些內容再次放到mark word。)
thread3 來競爭這把鎖,此時只有它自己,那麼thread3將會被設置爲Monitor的Owner,有且只能有一個Owner。
如果thread3持有鎖的過程中,如果thread4和thread5也來競爭鎖,就會添加到EntryList當中,此時線程將被阻塞(BLOCKED)。
當thread執行完同步代碼塊當中的內容,會喚醒EntryList當中的線程來競爭鎖,此競爭是非公平的。
另外,在WaitSet當中的thread1和thread2,其狀態是WAITING,表示他們之前獲得過鎖,但是條件不滿足,此處不講解,後面在分析wait,notify時講解。
二、synchronized原理分析
2.1 synchronized字節碼分析
在上面瞭解Monitor後,進入java當中的重點,synchronized的學習。
有如下代碼:
static final Object object = new Object();
static int i = 0;
public static void main(String[] args) {
synchronized (object) {
i++;
}
}
其字節碼如下所示:
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // 獲取鎖對象object
3: dup // 拷貝一份
4: astore_1 // 將拷貝的變量存儲到 slot 1中
5: monitorenter // 將Monitor指針設置到lock對象的Mark Word
6: getstatic #3 // 獲取靜態變量i
9: iconst_1 // 準備常數 1
10: iadd // 執行++ 操作
11: putstatic #3 // 將i當前值賦值給靜態變量
14: aload_1 // 獲取對象鎖
15: monitorexit // 重置對象的Mark Word,喚醒EntryList當中的線程
16: goto 24 // 跳轉到24 行
19: astore_2 // 將異常存儲到 slot 2 中
20: aload_1 // 獲取鎖對象
21: monitorexit // 重置對象的Mark Word,喚醒EntryList當中的線程
22: aload_2 // 獲取slot 2中的異常
23: athrow // 拋出異常
24: return // 方法返回
Exception table:
from to target type
6 16 19 any // 6 ~ 16 行爲正常代碼運行邏輯,如果在這之間發生了異常,則代碼跳轉值第19行
19 22 19 any // 19 ~ 22 是異常時,代碼執行的邏輯
關於上面字節碼的意思都在註釋中。
注意:在方法層面上的鎖,在字節碼當中不會有鎖體現,如下:
static final Object object = new Object();
static int i = 0;
public static synchronized void add() {
i++;
}
public static void main(String[] args) {
add();
}
字節碼如下:
public static synchronized void add();
Code:
0: getstatic #2 // Field i:I
3: iconst_1
4: iadd
5: putstatic #2 // Field i:I
8: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #3 // Method add:()V
3: return
2.2 輕量級鎖
如開篇展示的對象Mark Word,共有5種鎖的狀態, 我們本小節講解其中之一,輕量級鎖相關的內容。
輕量級鎖是指在滿足一定的條件內,使用CAS(自旋)來嘗試獲取對象鎖的一種機制,如果超過以下條件,則會膨脹爲重量級鎖:
1)在jdk1.6前,默認10次,可通過-XX:PreBlockSpin來修改,或者自旋線程數超過CPU核數的一半。
2)jdk1.6之後,引入了自適應自旋鎖,次數並非一成不變。根據獲取鎖的成功率來決定是否能有更長的等待時間。
輕量級鎖仍然是使用synchronized,用戶其實是無感知的。
2.2.1 輕量級鎖的上鎖和釋放鎖
假設當前有一把對象鎖lock,兩個方法使用同一把鎖,且add當中會調用sub()方法,如下所示:
static final Object lock = new Object();
public void add(){
synchronized (lock){
sub();
}
}
public void sub(){
synchronized (lock){
}
}
- 當線程嘗試獲取這把鎖的時候,會創建鎖記錄(Lock Record),每個線程的棧幀(線程執行到的方法,都會生成對應的棧幀),都會包含一個鎖記錄的結構,可以用來存儲對象的Mark Word,如下所示:
- 讓鎖記錄中 Object Reference 指向鎖對象,並嘗試用 CAS 替換 Object 的 Mark Word,將 Mark Word 的值存入鎖記錄(如圖上的1),並將鎖記錄的地址存入Object的Mark Word(如圖上的2),如下所示:
- 如果CAS成功,對象的Mark Word將會存儲Lock Record 地址 和 鎖狀態 00,如下圖所示:
- 如果CAS失敗,此時會有兩種情況:
- 如果是其他線程已經持有了該輕量級鎖,則表示發生競爭,此時進入鎖膨脹。
- 如果是自己執行了 synchronized 鎖重入(就像我們示例代碼中的add()方法),那麼再添加一條 Lock Record 作爲重入的計數,只不過新添加的Lock Record中沒有Object的Mark word內容,爲null。如下所示:
當退出 synchronized 代碼塊(解鎖時)如果有取值爲 null 的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數減一,將null的Lock Record刪除。
-
當退出 synchronized 代碼塊(解鎖時)鎖記錄的值不爲 null,這時使用 CAS 將 Mark Word 的值恢復給對象頭。
- 成功,則解鎖成功。
- 失敗,說明輕量級鎖進行了鎖膨脹或已經升級爲重量級鎖,進入重量級鎖解鎖流程。