Java | synchronized 不同情況下的對象頭測試
synchronized 不同情況下的對象頭測試
測試環(huán)境
JDK:Oracle JDK 1.8.0_144
代碼依賴:
junit-jupiter-engine:5.8.1
slf4j-simple:1.7.32
jol-core:0.16
測試代碼
1import?java.util.concurrent.TimeUnit;
2import?org.junit.jupiter.api.Assertions;
3import?org.junit.jupiter.api.Test;
4import?org.openjdk.jol.info.ClassLayout;
5import?org.slf4j.Logger;
6import?org.slf4j.LoggerFactory;
7
8class?LockObject?{}
9
10class?SyncTest?{
11
12????private?static?final?Logger?log?=?LoggerFactory.getLogger(SyncTest.class);
13
14????@Test
15??void?testSynchronizedLock()?throws?InterruptedException?{
16????Object?lock?=?new?Object();
17????syncLock(lock);
18????Assertions.assertTrue(true);
19??}
20
21??void?syncLock(Object?lock)?{
22????log.info("加鎖前?{}",?ClassLayout.parseInstance(lock).toPrintable());
23????synchronized?(lock)?{
24????????log.info("加鎖中?{}",?ClassLayout.parseInstance(lock).toPrintable());
25????}
26????log.info("加鎖后?{}",?ClassLayout.parseInstance(lock).toPrintable());
27??}
28}
測試情況
這里通過改動 testSynchronizedLock 方法代碼進行測試,下面的測試情況只說明改動后的 testSynchronizedLock的代碼,其余代碼不再說明。因為只關(guān)注對象頭的變化,其余的值也省略了。
情況一:同線程直接調(diào)用
1void?testSynchronizedLock()?throws?InterruptedException?{
2????Object?lock?=?new?Object();
3????syncLock(lock);
4????Assertions.assertTrue(true);
5}
執(zhí)行結(jié)果為:
1加鎖前?0x0000000000000001?(non-biasable;?age:?0)
2加鎖中?0x0000700007830f10?(thin?lock:?0x0000700007830f10)
3加鎖后?0x0000000000000001?(non-biasable;?age:?0)
通過結(jié)果可以看到,加鎖前的對象頭是 0x0000000000000001,加鎖中是 0x0000700007830f10,加鎖后是 0x0000000000000001??粗赡懿惶靼?,這里簡單說下 64 位 jvm 的對象頭的分布情況
1|--------------------------------------------------------------------------------------------------------------|
2|??????????????????????????????????????????????Object?Header?(128?bits)????????????????????????????????????????|
3|--------------------------------------------------------------------------------------------------------------|
4|????????????????????????Mark?Word?(64?bits)????????????????????????????????????|??????Klass?Word?(64?bits)????|???????
5|--------------------------------------------------------------------------------------------------------------|
6|??unused:25?|?identity_hashcode:31?|?unused:1?|?age:4?|?biased_lock:1?|?lock:2?|?????OOP?to?metadata?object???|??無鎖
7|----------------------------------------------------------------------|--------|------------------------------|
8|??thread:54?|?????????epoch:2??????|?unused:1?|?age:4?|?biased_lock:1?|?lock:2?|?????OOP?to?metadata?object???|??偏向鎖
9|----------------------------------------------------------------------|--------|------------------------------|
10|?????????????????????ptr_to_lock_record:62????????????????????????????|?lock:2?|?????OOP?to?metadata?object???|??輕量鎖
11|----------------------------------------------------------------------|--------|------------------------------|
12|?????????????????????ptr_to_heavyweight_monitor:62????????????????????|?lock:2?|?????OOP?to?metadata?object???|??重量鎖
13|----------------------------------------------------------------------|--------|------------------------------|
14|??????????????????????????????????????????????????????????????????????|?lock:2?|?????OOP?to?metadata?object???|??GC
15|--------------------------------------------------------------------------------------------------------------|
16
lock: 鎖狀態(tài)標(biāo)記位,該標(biāo)記的值不同,整個mark word表示的含義不同。
biased_lock:偏向鎖標(biāo)記,為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。
從分布可以得出,看鎖標(biāo)記,直接看后 3 位即可
| biased_lock | lock | 16進制 | 狀態(tài) |
|---|---|---|---|
| 0 | 01 | 1 | 無鎖 |
| 1 | 01 | 5 | 偏向 |
| 0 | 00 | 0 | 輕量 |
| 0 | 10 | 2 | 重量 |
| 0 | 11 | 3 | GC |
加鎖前的對象頭是 0x0000000000000001,加鎖中是 0x0000700007830f10,加鎖后是 0x0000000000000001
從這種情況可以看出:加鎖前對象處于無鎖狀態(tài),加鎖中處于輕量鎖狀態(tài),釋放鎖后處于無鎖狀態(tài)
這種現(xiàn)象和我們想象的可能不太一樣,在網(wǎng)上找了資料如下:
JVM啟動時會進行一系列的復(fù)雜活動,比如裝載配置,系統(tǒng)類初始化等等。在這個過程中會使用大量 synchronized 關(guān)鍵字對對象加鎖,且這些鎖大多數(shù)都不是偏向鎖。為了減少初始化時間,JVM默認(rèn)延時加載偏向鎖。這個延時的時間大概為 4s 左右,具體時間因機器而異。當(dāng)然我們也可以設(shè)置 JVM 參數(shù) -XX:BiasedLockingStartupDelay=0 來取消延時加載偏向鎖。
從上面可以看出,JVM默認(rèn)延時加載偏向鎖,時間大于 4s,為了更好的驗證,下面的代碼直接按 10s 處理。
情況二:先獲取一次鎖,然后延遲 10s 再次獲取
這個主要為了驗證一下上面的結(jié)論
1void?testSynchronizedLock()?throws?InterruptedException?{
2????Object?lock?=?new?Object();
3????syncLock(lock);
4????TimeUnit.SECONDS.sleep(10);
5????syncLock(lock);
6????Assertions.assertTrue(true);
7}
日志輸出如下:
1加鎖前?0x0000000000000001?(non-biasable;?age:?0)
2加鎖中?0x00007000028aaf10?(thin?lock:?0x00007000028aaf10)
3加鎖后?0x0000000000000001?(non-biasable;?age:?0)
4
5加鎖前?0x0000000000000001?(non-biasable;?age:?0)
6加鎖中?0x00007000028aaf10?(thin?lock:?0x00007000028aaf10)
7加鎖后?0x0000000000000001?(non-biasable;?age:?0)
兩次獲取鎖都使用的輕量級鎖
情況三:延遲 10s 后在創(chuàng)建鎖對象后調(diào)用
1void?testSynchronizedLock()?throws?InterruptedException?{
2????TimeUnit.SECONDS.sleep(10);
3????Object?lock?=?new?Object();
4????syncLock(lock);
5????Assertions.assertTrue(true);
6}
日志如下:
1加鎖前?0x0000000000000005?(biasable;?age:?0)
2加鎖中?0x00007fb114010805?(biased:?0x0000001fec450042;?epoch:?0;?age:?0)
3加鎖后?0x00007fb114010805?(biased:?0x0000001fec450042;?epoch:?0;?age:?0)
從這種情況可以看出:加鎖前對象處于偏向鎖狀態(tài),加鎖中處于偏向鎖狀態(tài),釋放鎖后處于偏向鎖狀態(tài),不過在加鎖前,并沒有偏向任何線程
情況四:增加 BiasedLockingStartupDelay=0 參數(shù)
1void?testSynchronizedLock()?throws?InterruptedException?{
2??Object?lock?=?new?Object();
3??syncLock(lock);
4??Assertions.assertTrue(true);
5}
日志
1加鎖前?0x0000000000000005?(biasable;?age:?0)
2加鎖中?0x00007fd650009005?(biased:?0x0000001ff5940024;?epoch:?0;?age:?0)
3加鎖后?0x00007fd650009005?(biased:?0x0000001ff5940024;?epoch:?0;?age:?0)
從這種情況可以看出:加鎖前對象處于偏向鎖狀態(tài),加鎖中處于偏向鎖狀態(tài),釋放鎖后處于偏向鎖狀態(tài),不過在加鎖前,并沒有偏向任何線程
從上述四種情況可以得出:
默認(rèn)情況 JVM 會延遲啟動偏向鎖功能,在 JVM 啟用偏向鎖功能前創(chuàng)建的鎖對象,直接使用輕量級鎖開始獲取鎖,而不會通過輕量級鎖階段。如果關(guān)閉延遲功能,可以使用 -XX:BiasedLockingStartupDelay=0 參數(shù)
后面的測試情況使用 TimeUnit.SECONDS.sleep(10); 來實現(xiàn)和
-XX:BiasedLockingStartupDelay=0的效果
情況五:同線程多次調(diào)用
1void?testSynchronizedLock()?throws?InterruptedException?{
2????TimeUnit.SECONDS.sleep(10);
3????Object?lock?=?new?Object();
4????syncLock(lock);
5????syncLock(lock);
6????Assertions.assertTrue(true);
7}
日志如下:
1加鎖前?0x0000000000000005?(biasable;?age:?0)
2加鎖中?0x0000023099602005?(biased:?0x000000008c265808;?epoch:?0;?age:?0)
3加鎖后?0x0000023099602005?(biased:?0x000000008c265808;?epoch:?0;?age:?0)
4
5加鎖前?0x0000023099602005?(biased:?0x000000008c265808;?epoch:?0;?age:?0)
6加鎖中??0x0000023099602005?(biased:?0x000000008c265808;?epoch:?0;?age:?0)
7加鎖后?0x0000023099602005?(biased:?0x000000008c265808;?epoch:?0;?age:?0)
從日志可以看出,第一次加鎖時,使用的偏向鎖,加鎖后偏向于 0x000000008c265808 第二次加鎖時,因為還在同一線程內(nèi),偏向鎖指向還是一樣,則直接獲取鎖,不進行鎖升級。
情況六:多線程無競爭兩次調(diào)用
1void?testSynchronizedLock()?throws?InterruptedException?{
2????TimeUnit.SECONDS.sleep(10);
3????Object?lock?=?new?Object();
4????syncLock(lock);
5????Thread?thread?=?new?Thread(()?->?syncLock(lock));
6????thread.start();
7????thread.join();
8????Assertions.assertTrue(true);
9}
日志如下:
1加鎖前?0x0000000000000005?(biasable;?age:?0)
2加鎖中?0x00000264e4573005?(biased:?0x00000000993915cc;?epoch:?0;?age:?0)
3加鎖后??0x00000264e4573005?(biased:?0x00000000993915cc;?epoch:?0;?age:?0)
4
5加鎖前?0x00000264e4573005?(biased:?0x00000000993915cc;?epoch:?0;?age:?0)
6加鎖中?0x000000d0c6dff748?(thin?lock:?0x000000d0c6dff748)
7加鎖后?0x0000000000000001?(non-biasable;?age:?0)
從日志可以看出,第一次加鎖時,使用的偏向鎖,第二次加鎖時使用的輕量級鎖(8的二進制時 1000),從中可以得出,即使沒有競爭關(guān)系,只要有一個線程加過鎖,那另一個線程再加鎖就會變成輕量級鎖,從最后一次日志可以看出,最終又變成了無鎖狀態(tài)
情況七:多線程無競爭很多次調(diào)用
1void?testSynchronizedLock()?throws?InterruptedException?{
2????TimeUnit.SECONDS.sleep(10);
3????Object?lock?=?new?Object();
4????syncLock(lock);
5????Thread?thread?=?new?Thread(()?->?syncLock(lock));
6????thread.start();
7????thread.join();
8????syncLock(lock);
9????thread?=?new?Thread(()?->?syncLock(lock));
10????thread.start();
11????thread.join();
12????Assertions.assertTrue(true);
13}
日志
1加鎖前?0x0000000000000005?(biasable;?age:?0)
2加鎖中??0x0000028212c72005?(biased:?0x00000000a084b1c8;?epoch:?0;?age:?0)
3加鎖后?0x0000028212c72005?(biased:?0x00000000a084b1c8;?epoch:?0;?age:?0)
4
5加鎖前?0x0000028212c72005?(biased:?0x00000000a084b1c8;?epoch:?0;?age:?0)
6加鎖中?0x000000ca803fefd8?(thin?lock:?0x000000ca803fefd8)
7加鎖后?0x0000000000000001?(non-biasable;?age:?0)
8
9加鎖前?0x0000000000000001?(non-biasable;?age:?0)
10加鎖中?0x000000cafeefc8a0?(thin?lock:?0x000000cafeefc8a0)
11加鎖后?0x0000000000000001?(non-biasable;?age:?0)
12
13加鎖前?0x0000000000000001?(non-biasable;?age:?0)
14加鎖中??0x000000ca803ff308?(thin?lock:?0x000000ca803ff308)
15加鎖后?0x0000000000000001?(non-biasable;?age:?0)
結(jié)果就是驗證了,輕量級鎖是可以轉(zhuǎn)換成無鎖的
情況八:多線程有競爭調(diào)用
1void?testSynchronizedLock()?throws?InterruptedException?{
2????TimeUnit.SECONDS.sleep(10);
3????Object?lock?=?new?Object();
4????syncLock(lock);
5????Thread?thread?=?new?Thread(()?->?syncLock(lock));
6????Thread?thread2?=?new?Thread(()?->?syncLock(lock));
7????thread.start();
8????thread2.start();
9
10????thread.join();
11????thread2.join();
12????Assertions.assertTrue(true);
13}
14
15void?syncLock(Object?lock)?{
16????log.info("currentThread?{}",?Thread.currentThread().getId());
17????log.info("加鎖前?{}",?ClassLayout.parseInstance(lock).toPrintable());
18????synchronized?(lock)?{
19????????try?{
20????????????TimeUnit.SECONDS.sleep(10);
21????????}?catch?(InterruptedException?e)?{
22????????????e.printStackTrace();
23????????}
24????????log.info("加鎖中?{}",?ClassLayout.parseInstance(lock).toPrintable());
25????}
26????log.info("加鎖后?{}",?ClassLayout.parseInstance(lock).toPrintable());
27}
日志如下:
1加鎖前?0x0000000000000005?(biasable;?age:?0)
2加鎖中?0x000001b16e421005?(biased:?0x000000006c5b9084;?epoch:?0;?age:?0)
3加鎖后??0x000001b16e421005?(biased:?0x000000006c5b9084;?epoch:?0;?age:?0)
4
5加鎖前?0x000001b16e421005?(biased:?0x000000006c5b9084;?epoch:?0;?age:?0)
6加鎖前?0x000001b16e421005?(biased:?0x000000006c5b9084;?epoch:?0;?age:?0)
7加鎖中?0x000001b10b4f0fba?(fat?lock:?0x000001b10b4f0fba)
8加鎖后?0x000001b10b4f0fba?(fat?lock:?0x000001b10b4f0fba)
9加鎖中?0x000001b10b4f0fba?(fat?lock:?0x000001b10b4f0fba)
10加鎖后?0x000001b10b4f0fba?(fat?lock:?0x000001b10b4f0fba)
從日志可以看出,顯示偏向,然后是重量級鎖,最后沒有變成無鎖
