前言java
本文繼續【Java併發之synchronized關鍵字深度解析(一)】一文而來,着重介紹synchronized幾種鎖的特性。併發
1、對象頭結構及鎖狀態標識jvm
synchronized關鍵字是如何實現的給對象加鎖?首先咱們要了解一下java中對象的組成。java中的對象由3部分組成,第一部分是對象頭,第二部分是實例數據,第三部分是對齊填充。ide
對齊填充:jvm規定對象的起始內存地址必須是8字節的整數倍,若是不夠的話就用佔位符來填充,此部分佔位符就是對齊填充;測試
實例數據:實例數據是對象存儲的真正有效的信息-對象的成員變量信息(包括繼承自父類的);spa
對象頭:對象頭由兩部分組成,第一部分是對象的運行時數據(Mark Word),包括哈希嗎、鎖偏向標識、鎖類型、GC分代年齡、偏向線程id等;第二部分是對象的類型指針(Kclass Word),用於去堆中定位對象的實例數據和方法區中的類型數據。java對象的公共特性都在對象頭中存放。操作系統
對象頭存儲內容以下所示(以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 |--------------------------------------------------------------------------------------------------------------|
其中lock:2表示有2bit控制鎖類型,biased_lock:1表示1bit控制偏向鎖狀態,對應關係以下所示:設計
01:無鎖(前面偏向鎖狀態爲0時表示未鎖定)指針
01:可偏向(前面偏向鎖狀態爲1時表示可偏向)
00:輕量級鎖
10:重量級鎖
11:GC標記
看到前兩種狀態時可能道友們會有些迷糊,先彆着急,此處只要記住JVM的設計者們想用01狀態來表示兩種狀況(無鎖和可偏向),可是地球人都知道一個字符是沒法作到標識兩種狀態的,因此他們就把前面一位暫時用不到的bit歸入進來,用前一位的值是0仍是1來區分是無鎖仍是可偏向。
2、鎖的信息打印
下面咱們先用代碼驗證一下這幾種鎖的存在(JVM默認開啓偏向鎖,默認的偏向鎖啓動時間爲4-5秒後,因此先讓主線程睡5秒再加鎖能保證對象處於偏向鎖的狀態,此處也能夠在VM Options中添加參數 【-XX:BiasedLockingStartupDelay=0】來讓JVM取消延遲啓動偏向鎖(本文的示例均未設置此參數),其效果跟不改變VM Options只在main方法中讓主線程先睡眠5秒是同樣的)
此外,要打印對象存儲空間須要引入openjdk的jar包依賴
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
User對象代碼:
1 public class User {
2 public String name; 3 public byte age; 4 }
萬事具有,下面開始測試:
一、無鎖狀態
先不睡眠五秒,此時偏向鎖未開啓,因此對象都是無鎖狀態(未加synchronized的狀況下),打印無鎖狀態的對象(鎖標識001)
1 package com.mybokeyuan.lockDemo;
2
3 import org.openjdk.jol.info.ClassLayout; 4 5 public class LockClientTest { 6 public static void main(String[] args) { 7 User user = new User(); 8 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 9 } 10 }
輸出結果:
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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 c1 00 20 (01000011 11000001 00000000 00100000) (536920387) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
下面咱們來解讀一下這個打印結果。
經過TYPE DESCRIPTION能夠知道,前三行打印的是對象頭(object header),那麼後面四行就是對象的實例數據和對其填充了。
先看第一行,VALUE中,標紅的001表示當前對象是無鎖狀態,前面的0對應咱們上面講的可偏向鎖狀態爲非偏向鎖(若是是1表示偏向鎖)。第三行存放的是對象指針。
第四行和第六行存放的是對象的兩個成員變量,第五行空間用於填充age變量;第七行就是咱們所說的對齊填充,使對象內存空間湊齊8字節的整數倍。
二、偏向鎖狀態
加上睡眠5秒
1 package com.mybokeyuan.lockDemo;
2
3 import org.openjdk.jol.info.ClassLayout; 4 5 public class LockClientTest { 6 public static void main(String[] args) { 7 // 先睡眠5秒,保證開啓偏向鎖 8 try { 9 Thread.sleep(5000); 10 } catch (InterruptedException e) { // -XX:-UseBiasedLocking 11 e.printStackTrace(); // -XX:BiasedLockingStartupDelay=0 12 } 13 User user = new User(); 14 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 15 } 16 }
看看打印結果:
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
能夠看到,鎖狀態爲101可偏向鎖狀態了,只是因爲未用synchronized加鎖,因此線程id是空的。其他數據跟上述無鎖狀態同樣。
偏向鎖帶線程id狀況,代碼以下:
1 package com.mybokeyuan.lockDemo;
2
3 import org.openjdk.jol.info.ClassLayout; 4 5 public class LockClientTest { 6 public static void main(String[] args) { 7 // 先睡眠5秒,保證開啓偏向鎖 8 try { 9 Thread.sleep(5000); 10 } catch (InterruptedException e) { // -XX:-UseBiasedLocking 11 e.printStackTrace(); // -XX:BiasedLockingStartupDelay=0 12 } 13 User user = new User(); 14 synchronized (user) { 15 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 16 } 17 } 18 }
輸出結果:
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e8 d0 02 (00000101 11101000 11010000 00000010) (47245317) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
可見第一行中後面再也不是0了,有了線程id的值。
三、輕量級鎖狀態
再看看輕量鎖,不睡眠5秒,直接用synchronized給對象加鎖,此時觸發的就是輕量鎖。代碼以下:
1 package com.mybokeyuan.lockDemo;
2
3 import org.openjdk.jol.info.ClassLayout; 4 5 public class LockClientTest { 6 public static void main(String[] args) { 7 User user = new User(); 8 synchronized (user) { 9 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 10 } 11 } 12 }
打印結果:
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) e8 f2 47 03 (11101000 11110010 01000111 00000011) (55046888) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
能夠看到鎖的標識位爲000,輕量級鎖
四、重量級鎖狀態
最後看一下重量級鎖,只有在鎖競爭的時候纔會變爲重量級鎖,代碼以下:
1 package com.mybokeyuan.lockDemo;
2
3 import org.openjdk.jol.info.ClassLayout; 4 5 public class LockClientTest { 6 public static void main(String[] args) { 7 User user = new User(); 8 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 9 Thread t1 = new Thread(() -> { 10 synchronized (user) { 11 try { 12 Thread.sleep(5000);// 睡眠,創造競爭條件 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 } 17 }); 18 t1.start(); 19 Thread t2 = new Thread(() -> { 20 synchronized (user) { 21 try { 22 Thread.sleep(1000); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 }); 28 t2.start(); 29 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 30 } 31 }
輸出結果爲:
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 8a e4 ba 02 (10001010 11100100 10111010 00000010) (45802634) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
能夠看到鎖狀態爲010,重量級鎖。
五、調用hashCode會取消偏向
此外,若是經過Object對象的本地hashCode方法來獲取對象的hashCode值,會使對象取消偏向鎖狀態
1 public class LockClientTest {
2 public static void main(String[] args) { 3 // 先睡眠5秒,保證開啓偏向鎖 4 try { 5 Thread.sleep(5000); 6 } catch (InterruptedException e) { // -XX:-UseBiasedLocking 7 e.printStackTrace(); // -XX:BiasedLockingStartupDelay=0 8 } 9 User user = new User(); 10 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 11 System.out.println(user.hashCode()); 12 System.out.println(ClassLayout.parseInstance(user).toPrintable()); 13 14 } 15 }
打印結果:
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 44 c1 00 20 (01000100 11000001 00000000 00100000) (536920388) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total 460332449 com.mybokeyuan.lockDemo.User object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 a1 1d 70 (00000001 10100001 00011101 01110000) (1880989953) 4 4 (object header) 1b 00 00 00 (00011011 00000000 00000000 00000000) (27) 8 4 (object header) 44 c1 00 20 (01000100 11000001 00000000 00100000) (536920388) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
能夠看到,計算完對象的hashCode以後,該對象當即從偏向鎖狀態變爲了無鎖狀態,即便後續給對象加鎖,該對象也只會進入輕量級或者重量級鎖狀態,不會再進入偏向狀態了。由於該對象一旦進行Object的hashCode計算,那麼對象頭中會保存這個hashCode,此時再也沒法存放偏向線程的id了(由於對象頭的長度沒法同時存放hashCode和偏向線程id),因此此後該對象沒法再進入偏向鎖狀態。
3、鎖膨脹過程
到這裏,咱們一塊兒看完了synchronized給對象加的各類鎖狀態以及觸發場景,下面咱們梳理一下它們之間的關係。
JVM啓動後會默認開啓偏向鎖(默認4-5秒後開啓),開啓後,全部新建對象的對象頭中都標識爲101可偏向狀態,且偏向線程id爲0,表示處於初始化的偏向鎖狀態。此後一旦有線程對該對象使用了synchronized加鎖,那麼就會進入偏向鎖狀態,偏向線程id記錄當前線程id;若是走完同步塊以後,有另外一個線程對該對象加鎖,那麼膨脹爲輕量級鎖,若是未走完同步塊就有另外一個線程試圖給該對象加鎖,那麼會直接膨脹爲(中間會有一個自旋鎖的過程,此處略去)重量級鎖。
一、開啓偏向鎖
開啓偏向的鎖膨脹草圖
下面演示一下對象從偏向鎖膨脹爲輕量級鎖的過程:
package com.mybokeyuan.lockDemo;
import org.openjdk.jol.info.ClassLayout; public class LockClientTest { public static void main(String[] args) throws Exception { // 先睡眠5秒,保證開啓偏向鎖 try { Thread.sleep(5000); } catch (InterruptedException e) { // -XX:-UseBiasedLocking e.printStackTrace(); // -XX:BiasedLockingStartupDelay=0 } User user = new User(); Thread t1 = new Thread(() -> { synchronized (user) { System.out.println(ClassLayout.parseInstance(user).toPrintable()); } }); t1.start(); t1.join(); // 確保t1執行完了再執行當前主線程 synchronized (user) { System.out.println(ClassLayout.parseInstance(user).toPrintable()); } System.out.println(ClassLayout.parseInstance(user).toPrintable()); } }
打印結果以下,能夠看到user對象先是偏向鎖,而後變爲輕量級鎖,最後走完同步塊釋放鎖變爲無鎖狀態。
com.mybokeyuan.lockDemo.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 48 6c 1a (00000101 01001000 01101100 00011010) (443303941) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 44 c1 00 20 (01000100 11000001 00000000 00100000) (536920388) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total com.mybokeyuan.lockDemo.User object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) c0 f0 1f 02 (11000000 11110000 00011111 00000010) (35647680) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 44 c1 00 20 (01000100 11000001 00000000 00100000) (536920388) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total com.mybokeyuan.lockDemo.User object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 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) 44 c1 00 20 (01000100 11000001 00000000 00100000) (536920388) 12 1 byte User.age 0 13 3 (alignment/padding gap) 16 4 java.lang.String User.name null 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
二、關閉偏向鎖
若是經過參數設置JVM不開啓偏向鎖,那麼新建立的對象是001無鎖狀態,遇到synchronized同步塊會變爲輕量級鎖,遇到鎖競爭變爲重量級鎖。
關閉偏向的鎖膨脹草圖
4、重量級鎖原理
Java中synchronized的重量級鎖,是基於進入和退出Monitor對象實現的。在編譯時會將同步塊的開始位置插入monitorenter指令,在結束位置插入monitorexit指令。當線程執行到monitorenter指令時,會嘗試獲取對象所對應的Monitor全部權,若是獲取到了,即獲取到了鎖,會在Monitor的owner中存放當前線程的id,這樣它將處於鎖定狀態,除非退出同步塊,不然其餘線程沒法獲取到這個Monitor。
後記
下一篇將是本小系列的最後一篇,着重介紹synchronized的批量重定向和批量撤銷機制,若有不確切之處,歡迎繼續拍磚。