1、小故事
故事角色
-
老王 - JVM
-
小南 - 執行緒
-
小女 - 執行緒
-
房間 - 物件
-
房間門上 - 防盜鎖 - Monitor-重量級鎖
-
房間門上 - 小南書包 - 輕量級鎖
-
房間門上 - 刻上小南大名 - 偏向鎖 -物件專屬於某個執行緒使用
-
批次重刻名 - 一個類的偏向鎖撤銷到達 20 閾值 -批次重偏向
-
不能刻名字 - 批次撤銷該類物件的偏向鎖,設定該類不可偏向
但是,很多情況下沒人跟他來競爭房間的使用權。小女是要用房間,但使用的時間上是錯開的,小南白天用,小女晚上用。每次上鎖太麻煩了,有沒有更簡單的辦法呢?
小南和小女商量了一下,約定不鎖門了,而是誰用房間,誰把自己的書包掛在門口,但他們的書包樣式都一樣,因此每次進門前得翻翻書包,看課本是誰的,如果是自己的,那麼就可以進門,這樣省的上鎖解鎖了。萬一書包不是自己的,那麼就在門外等,並通知對方下次用鎖門的方式。
後來,小女回老家了,很長一段時間都不會用這個房間。小南每次還是掛書包,翻書包,雖然比鎖門省事了,但仍然覺得麻煩。
於是,小南乾脆在門上刻上了自己的名字:【小南專屬房間,其它人勿用】,下次來用房間時,只要名字還在,那麼說明沒人打擾,還是可以安全地使用房間。如果這期間有其它人要用這個房間,那麼由使用者將小南刻的名字擦掉,升級為掛書包的方式。
同學們都放假回老家了,小南就膨脹了,在 20 個房間刻上了自己的名字,想進哪個進哪個。後來他自己放假回老家了,這時小女回來了(她也要用這些房間),結果就是得一個個地擦掉小南刻的名字,升級為掛書包的方式。老王覺得這成本有點高,提出了一種批次重刻名的方法,他讓小女不用掛書包了,可以直接在門上刻上自己的名字
後來,刻名的現象越來越頻繁,老王受不了了:算了,這些房間都不能刻名了,只能掛書包----設定該類不可偏向
節碼指令中有所體現
2.1 輕量級鎖
輕量級鎖的使用場景:如果一個物件雖然有多執行緒要加鎖,但加鎖的時間是錯開的(也就是沒有競爭),那麼可以使用輕量級鎖來最佳化。
如果有競爭,輕量級鎖會升級為重量級鎖。
輕量級鎖對使用者是透明的,即語法仍然是 synchronized
假設有兩個方法同步塊,利用同一個物件加鎖
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步塊 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步塊 B
}
}
-
建立鎖記錄(Lock Record)物件,每個執行緒都的棧幀都會包含一個鎖記錄的結構,內部可以儲存鎖定物件的 Mark Word
-
讓鎖記錄中 Object reference 指向鎖物件,並嘗試用 cas(compare and swap,原子性) 替換 Object 的 Mark Word,將 Mark Word 的值存入鎖記錄
-
如果 cas(compare and swap,原子性) 替換成功,物件頭中儲存了
鎖記錄地址和狀態 00
,表示由該執行緒給物件加鎖,這時圖示如下
-
如果 cas(compare and swap,原子性) 失敗,有兩種情況
-
如果是其它執行緒已經持有了該 Object 的輕量級鎖,這時表明有競爭,進入鎖膨脹過程
-
如果是自己執行了 synchronized 鎖重入,那麼再新增一條 Lock Record 作為重入的計數,如示例程式碼
-
-
當退出 synchronized 程式碼塊(解鎖時)如果有取值為 null 的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數減一(鎖重入的數量)
-
當退出 synchronized 程式碼塊(解鎖時)鎖記錄的值不為 null,這時使用 cas(compare and swap,原子性) 將 Mark Word 的值恢復給物件頭
-
成功,則解鎖成功
-
失敗,說明輕量級鎖進行了鎖膨脹或已經升級為重量級鎖,進入重量級鎖解鎖流程
-
2.2 鎖膨脹
如果在嘗試加輕量級鎖的過程中,CAS 操作無法成功,這時一種情況就是有其它執行緒為此物件加上了輕量級鎖(有競爭),這時需要進行鎖膨脹,將輕量級鎖變為重量級鎖。
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步塊
}
}
-
當 Thread-1 進行輕量級加鎖時,Thread-0 已經對該物件加了輕量級鎖
-
這時 Thread-1 加輕量級鎖失敗,進入鎖膨脹流程
-
即為 Object 物件申請 Monitor 鎖,讓 Object 指向重量級鎖地址
-
然後自己進入 Monitor 的 EntryList BLOCKED
-
-
當 Thread-0 退出同步塊解鎖時,使用 cas 將 Mark Word 的值恢復給物件頭,失敗。這時會進入重量級解鎖流程,即按照 Monitor 地址找到 Monitor 物件,設定 Owner 為 null,喚醒 EntryList 中 BLOCKED 執行緒
2.3 自旋最佳化
重量級鎖競爭的時候,還可以使用自旋來進行最佳化,如果當前執行緒自旋成功(即這時候持鎖執行緒已經退出了同步塊,釋放了鎖),這時當前執行緒就可以避免阻塞。
自旋重試成功的情況
執行緒 1 (core 1 上) | 物件 Mark | 執行緒 2 (core 2 上) |
---|---|---|
- | 10(重量鎖) | - |
訪問同步塊,獲取 monitor | 10(重量鎖)重量鎖指標 | - |
成功(加鎖) | 10(重量鎖)重量鎖指標 | - |
執行同步塊 | 10(重量鎖)重量鎖指標 | - |
執行同步塊 | 10(重量鎖)重量鎖指標 | 訪問同步塊,獲取 monitor |
執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
執行完畢 | 10(重量鎖)重量鎖指標 | 自旋重試 |
成功(解鎖) | 01(無鎖) | 自旋重試 |
- | 10(重量鎖)重量鎖指標 | 成功(加鎖) |
- | 10(重量鎖)重量鎖指標 | 執行同步塊 |
- | ... | ... |
執行緒2自旋重試3次成功加鎖,這樣就不會陷入阻塞。
自旋重試失敗的情況
執行緒 1(core 1 上) | 物件 Mark | 執行緒 2(core 2 上) |
---|---|---|
- | 10(重量鎖) | - |
訪問同步塊,獲取 monitor | 10(重量鎖)重量鎖指標 | - |
成功(加鎖) | 10(重量鎖)重量鎖指標 | - |
執行同步塊 | 10(重量鎖)重量鎖指標 | - |
執行同步塊 | 10(重量鎖)重量鎖指標 | 訪問同步塊,獲取 monitor |
執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
執行同步塊 | 10(重量鎖)重量鎖指標 | 阻塞 |
- | ... | ... |
-
執行緒2一直處於自旋,最後處於阻塞狀態。
-
自旋會佔用 CPU 時間,單核 CPU 自旋就是浪費,多核 CPU 自旋才能發揮優勢。
-
在 Java 6 之後自旋鎖是自適應的,比如物件剛剛的一次自旋操作成功過,那麼認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智慧。
-
Java 7 之後不能控制是否開啟自旋功能
2.4 偏向鎖
輕量級鎖在沒有競爭時(就自己這個執行緒),每次鎖重入時仍然需要執行 CAS 操作。
Java 6 中引入了偏向鎖來做進一步最佳化:只有第一次使用 CAS 將執行緒 ID 設定到物件的 Mark Word 頭,之後發現這個執行緒 ID 是自己的就表示沒有競爭,不用重新 CAS。以後只要不發生競爭,這個物件就歸該執行緒所有
例如:
static final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步塊 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步塊 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
// 同步塊 C
}
}
2.4.1 偏向狀態
回憶一下物件頭格式
|--------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|--------------------------------------------------------------------|--------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal | // 正常
|--------------------------------------------------------------------|--------------------| // 偏向鎖
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased |
|--------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | 00 | Lightweight Locked | // 輕量級
|--------------------------------------------------------------------|--------------------| // 重量級
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked |
|--------------------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|--------------------------------------------------------------------|--------------------|
一個物件建立時:
-
如果開啟了偏向鎖(預設開啟),那麼物件建立後,markword 值為 0x05 即最後 3 位為 101,這時它的 thread、epoch、age 都為 0
-
偏向鎖是預設是延遲的,不會在程式啟動時立即生效,可以sleep 4s後檢視,如果想避免延遲,可以加 VM 引數
-XX:BiasedLockingStartupDelay=0
來禁用延遲 -
如果沒有開啟偏向鎖,那麼物件建立後,markword 值為 0x01 即最後 3 位為 001,這時它的 hashcode、age 都為 0,第一次用到 hashcode 時才會賦值
1) 測試延遲特性
2) 測試偏向鎖
class Dog {}
利用 jol 第三方工具來檢視物件頭資訊
pom檔案
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
程式碼(注意這一小節的程式碼瞭解一下即可,在本機是執行不成功的,注意看輸出就行了)
// 新增虛擬機器引數 -XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws IOException {
Dog d = new Dog();
ClassLayout classLayout = ClassLayout.parseInstance(d);
new Thread(() -> {
log.debug("synchronized 前");
System.out.println(classLayout.toPrintableSimple(true));
synchronized (d) {
log.debug("synchronized 中");
System.out.println(classLayout.toPrintableSimple(true));
}
log.debug("synchronized 後");
System.out.println(classLayout.toPrintableSimple(true));
}, "t1").start();
}
輸出
11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 //VM 引數 `-XX:BiasedLockingStartupDelay=0`後
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 // 與上面輸出的區別,根據物件頭格式檢視
11:08:58.121 c.TestBiased [t1] - synchronized 後
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 // 處於偏向鎖,執行緒id不變,除非有新的競爭
注意
處於偏向鎖的物件解鎖後,執行緒 id 仍儲存於物件頭中
3)測試禁用
在上面測試程式碼執行時在新增 VM 引數 -XX:-UseBiasedLocking
禁用偏向鎖
輸出
11:13:10.018 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 三條日誌後三位均不是101
11:13:10.021 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 // 處於輕量級鎖
11:13:10.021 c.TestBiased [t1] - synchronized 後
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 恢復成正常狀態
4) 測試 hashCode,新增VM 引數 -XX:BiasedLockingStartupDelay=0
來禁用延遲
-
正常狀態物件一開始是沒有 hashCode 的,第一次呼叫才生成
-
呼叫 hashCode 會導致偏向鎖被禁用。因為處於偏向鎖狀態的話已經儲存執行緒id,再去儲存hashcode,空間不夠,是儲存不下的。所以這時候的狀態會被改為正常狀態。另外輕量級鎖的hashcode儲存在棧幀中的鎖記錄中,重量級鎖的hashcode儲存在monitor物件中,解鎖時會還原。
2.4.2 撤銷 - 呼叫物件 hashCode
呼叫了物件的 hashCode,但偏向鎖的物件 MarkWord 中儲存的是執行緒 id,如果呼叫 hashCode 會導致偏向鎖被撤銷
-
輕量級鎖會在鎖記錄中記錄 hashCode
-
重量級鎖會在 Monitor 中記錄 hashCode
在呼叫 hashCode 後使用偏向鎖,記得去掉 -XX:-UseBiasedLocking
輸出
11:22:10.386 c.TestBiased [main] - 呼叫 hashCode:1778535015
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000
11:22:10.393 c.TestBiased [t1] - synchronized 後
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
2.4.3 撤銷 - 其它執行緒使用物件
當有其它執行緒使用偏向鎖物件時,會將偏向鎖升級為輕量級鎖
private static void test2() throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
// 鎖住當前類物件
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
// 如果不用 wait/notify 使用 join 必須開啟下面的註釋
// 因為:t1 執行緒不能結束,否則底層執行緒可能被 jvm 重用作為 t2 執行緒,底層執行緒 id 是一樣的
/*try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}*/
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
// 鎖住當前類物件
synchronized (TestBiased.class) {
try {
TestBiased.class.wait(); // 等待t1執行緒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}, "t2");
t2.start();
}
輸出
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 // 處於偏向鎖
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 // t2執行緒還未加鎖和t1狀態保持一致
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 // 處於輕量級鎖
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 解鎖後處於不可偏向
2.4.4 撤銷 - 呼叫 wait/notify
這種情況下也會撤銷偏向鎖。因為wait/notify只有重量級鎖才有。會將偏向鎖或者輕量級鎖升級為重量級鎖
public static void main(String[] args) throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("notify");
d.notify();
}
}, "t2").start();
}
輸出
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 // 偏向鎖
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 // 加鎖
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010 // 重量級鎖
2.4.5 批次重偏向
如果物件雖然被多個執行緒訪問,但沒有競爭,這時偏向了執行緒 T1 的物件仍有機會重新偏向 T2,重偏向會重置物件的 Thread ID
當撤銷偏向鎖閾值超過 20 次後,jvm 會這樣覺得,我是不是偏向錯了呢,於是會在給這些物件加鎖時重新偏向至加鎖執行緒
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}
輸出
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 // 偏向鎖
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 // 偏向t1鎖
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 // 撤銷偏向鎖,升級為輕量級鎖
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 // 處於不可偏向鎖,也就是正常狀態
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 // 第20個物件開始,全部處於偏向t2的偏向鎖
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 // 批次重偏向,後面所有都處於偏向t2的偏向鎖
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
2.4.6 批次撤銷
當撤銷偏向鎖閾值超過 40 次後,jvm 會這樣覺得,自己確實偏向錯了,根本就不該偏向。於是整個類的所有物件都會變為不可偏向的,新建的物件也是不可偏向的
這裡就不列印日誌了,可以自行思考
static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}
參考資料
2.5 鎖消除
鎖消除
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() throws Exception {
x++;
}
@Benchmark
public void b() throws Exception {
Object o = new Object();
synchronized (o) {
x++;
}
}
}
java -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.542 0.056 ns/op
c.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op
b是有一個加鎖的操作,那為什麼a耗時與b耗時幾乎別區別呢?是因為Java中有一個JIT(即時編譯器),會對於反覆執行的程式碼進行最佳化,b中o物件根本不會被共享,所以b中的synchronized是沒有任何意義的,所以Java就把鎖給消除了。這也是存在一個開關的,這個開關是預設開啟的,下面演示下把這個開關關閉掉。
java -XX:-EliminateLocks -jar benchmarks.jar
Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.507 0.108 ns/op
c.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op
可以看到耗時有明顯差距了。
鎖粗化
對相同物件多次加鎖,導致執行緒發生多次重入,可以使用鎖粗化方式來最佳化,這不同於之前的細分鎖的粒度。