synchronized 底層如何實現?什么是鎖升級、降級?
synchronized 代碼塊是由一對 monitorenter/monitorexit 指令實現的,Monitor 對象是同步的基本實現單元。
- https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#d5e13622
在Java6之前, Monitor的實現完全是依靠操作系統內部的互斥鎖,因為需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作。現代的( Oracle)JDK中,JVM對此進行了大刀闊斧地改進,提供了三種不同的 Monitor 實現,也就是常說的三種不同的鎖:偏斜鎖( Biased Locking)、輕量級鎖和重量級鎖,大大改進了其性能。
什么是鎖升級,降級?
所謂的鎖升級、降級,就是 JVM 優化 synchronized 運行的機制,當 JVM 監測到不同的競爭狀況是,會自動切換到不同的鎖實現。這種切換就是鎖的升級、降級。
對象的結構
說偏向鎖之前,需要理解對象的結構,對象由多部分構成的,對象頭,屬性字段、補齊區域等。所謂補齊區域是指如果對象總大小不是4字節的整數倍,會填充上一段內存地址使之成為整數倍。
偏向鎖又和對象頭密切相關,對象頭這部分在對象的最前端,包含兩部分或者三部分:Mark Words、Klass Words,如果對象是一個數組,那么還可能包含第三部分:數組的長度。
- Klass Word里面存的是一個地址,占32位或64位,是一個指向當前對象所屬于的類的地址,可以通過這個地址獲取到它的元數據信息。
- Mark Word需要重點說一下,這里面主要包含對象的哈希值、年齡分代、hashcode、鎖標志位等。
如果應用的對象過多,使用64位的指針將浪費大量內存。64位的JVM比32位的JVM多耗費50%的內存。 我們現在使用的64位 JVM會默認使用選項 +UseCompressedOops 開啟指針壓縮,將指針壓縮至32位。
以64位操作系統為例,對象頭存儲內容圖例
|--------------------------------------------------------------------------------------------------------------|
| Object Header (128 bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) |
|--------------------------------------------------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 無鎖
|----------------------------------------------------------------------|--------|------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 偏向鎖
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | 輕量鎖
|----------------------------------------------------------------------|--------|------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | 重量鎖
|----------------------------------------------------------------------|--------|------------------------------|
| | lock:2 | OOP to metadata object | GC
|--------------------------------------------------------------------------------------------------------------|
對象頭中的信息如何理解呢,舉個例子
從該對象頭中分析加鎖信息,MarkWordk為0x0000700009b96910,二進制為0xb00000000 00000000 01111111 11110000 11001000 00000000 01010011 11101010。 倒數第三位為"0",說明不是偏向鎖狀態,倒數兩位為"10",因此,是重量級鎖狀態,那么前面62位就是指向互斥量的指針。
basied_locklock狀態001無鎖101偏向鎖000輕量級鎖010重量級鎖011GC標記
- age:Java GC標記位對象年齡。
- identity_hashcode:對象標識Hash碼,采用延遲加載技術。當對象使用HashCode()計算后,并會將結果寫到該對象頭中。當對象被鎖定時,該值會移動到線程Monitor中。
- thread:持有偏向鎖的線程ID和其他信息。這個線程ID并不是JVM分配的線程ID號,和Java Thread中的ID是兩個概念。
- epoch:偏向時間戳。
- ptr_to_lock_record:指向棧中鎖記錄的指針。
- ptr_to_heavyweight_monitor:指向線程Monitor的指針。
無鎖
A a = new A();System.out.println(ClassLayout.parseInstance(a).toPrintable());
可以看到最后 00000001 basied_lock = 0, lock =01 表示無鎖
JavaThread.synchronizestructure.A object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)12 1 boolean A.flag false13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
偏斜鎖
當沒有競爭出現時,默認使用偏斜鎖。 JVM 會利用 CAS 操作在對象頭上的 Mark Word 部分設置線程 ID ,以表示對象偏向當前線程。所以并不涉及真正的互斥鎖,這樣做的假設是基于在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏斜鎖可以降低無竟爭開銷。 測試代碼:
Thread.sleep(5000);
A a = new A();
synchronized (a) {System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
運行結果:
JavaThread.synchronizestructure.A object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 05 28 8d 02 (00000101 00101000 10001101 00000010) (42805253)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)12 1 boolean A.flag false13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
00000101 中 basied_lock = 1, lock =01 表示偏斜鎖
輕量級鎖
thread1中依舊輸出偏向鎖,主線程獲取對象A時,thread1雖然已經退出同步代碼塊,但主線程和thread1仍然為鎖的交替競爭關系。故此時主線程輸出結果為輕量級鎖。 測試代碼:
Thread.sleep(5000);
A a = new A();Thread thread1= new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread1 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向鎖}}
};
thread1.start();
thread1.join();
Thread.sleep(10000);synchronized (a){System.out.println("main locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());//輕量鎖
}
}
運行結果:
JavaThread.synchronizestructure.A object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 05 40 e9 19 (00000101 01000000 11101001 00011001) (434716677)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)12 1 boolean A.flag false13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes totalmain locking
JavaThread.synchronizestructure.A object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 38 f6 c7 02 (00111000 11110110 11000111 00000010) (46659128)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)12 1 boolean A.flag false13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
00000101 依然是偏向鎖,00111000 是輕量級鎖
重量級鎖
thread1 和 thread2?同時競爭對象a,此時輸出結果為重量級鎖
測試代碼:
Thread.sleep(5000);
A a = new A();
Thread thread1 = new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread1 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());try {//讓線程晚點兒死亡,造成鎖的競爭Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
};
Thread thread2 = new Thread(){@Overridepublic void run() {synchronized (a){System.out.println("thread2 locking");System.out.println(ClassLayout.parseInstance(a).toPrintable());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
};
thread1.start();
thread2.start();
運行結果:
thread2 locking
JavaThread.synchronizestructure.A object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 7a f5 99 17 (01111010 11110101 10011001 00010111) (395965818)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) 62 c1 00 20 (01100010 11000001 00000000 00100000) (536920418)12 1 boolean A.flag false13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
01111010 basied_lock = 0 lock=10 重量級鎖