故事角色
老王 - JVM
小南 - 執行緒
小女 - 執行緒
房間 - 物件
房間門上 - 防盜鎖 - Monitor-重量級鎖
房間門上 - 小南書包 - 輕量級鎖
房間門上 - 刻上小南大名 - 偏向鎖 -物件專屬於某個執行緒使用
批次重刻名 - 一個類的偏向鎖復原到達 20 閾值 -批次重偏向
不能刻名字 - 批次復原該類物件的偏向鎖,設定該類不可偏向
但是,很多情況下沒人跟他來競爭房間的使用權。小女是要用房間,但使用的時間上是錯開的,小南白天用,小女晚上用。每次上鎖太麻煩了,有沒有更簡單的辦法呢?
小南和小女商量了一下,約定不鎖門了,而是誰用房間,誰把自己的書包掛在門口,但他們的書包樣式都一樣,因此每次進門前得翻翻書包,看課本是誰的,如果是自己的,那麼就可以進門,這樣省的上鎖解鎖了。萬一書包不是自己的,那麼就在門外等,並通知對方下次用鎖門的方式。
後來,小女回老家了,很長一段時間都不會用這個房間。小南每次還是掛書包,翻書包,雖然比鎖門省事了,但仍然覺得麻煩。
於是,小南乾脆在門上刻上了自己的名字:【小南專屬房間,其它人勿用】,下次來用房間時,只要名字還在,那麼說明沒人打擾,還是可以安全地使用房間。如果這期間有其它人要用這個房間,那麼由使用者將小南刻的名字擦掉,升級為掛書包的方式。
同學們都放假回老家了,小南就膨脹了,在 20 個房間刻上了自己的名字,想進哪個進哪個。後來他自己放假回老家了,這時小女回來了(她也要用這些房間),結果就是得一個個地擦掉小南刻的名字,升級為掛書包的方式。老王覺得這成本有點高,提出了一種批次重刻名的方法,他讓小女不用掛書包了,可以直接在門上刻上自己的名字
後來,刻名的現象越來越頻繁,老王受不了了:算了,這些房間都不能刻名了,只能掛書包----設定該類不可偏向
節碼指令中有所體現
輕量級鎖的使用場景:如果一個物件雖然有多執行緒要加鎖,但加鎖的時間是錯開的(也就是沒有競爭),那麼可以使用輕量級鎖來優化。
如果有競爭,輕量級鎖會升級為重量級鎖。
輕量級鎖對使用者是透明的,即語法仍然是 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 的值恢復給物件頭
成功,則解鎖成功
失敗,說明輕量級鎖進行了鎖膨脹或已經升級為重量級鎖,進入重量級鎖解鎖流程
如果在嘗試加輕量級鎖的過程中,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 執行緒
重量級鎖競爭的時候,還可以使用自旋來進行優化,如果當前執行緒自旋成功(即這時候持鎖執行緒已經退出了同步塊,釋放了鎖),這時當前執行緒就可以避免阻塞。
自旋重試成功的情況
執行緒 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 之後不能控制是否開啟自旋功能
輕量級鎖在沒有競爭時(就自己這個執行緒),每次鎖重入時仍然需要執行 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
}
}
回憶一下物件頭格式
|--------------------------------------------------------------------|--------------------|
| 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 時才會賦值
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 仍儲存於物件頭中
在上面測試程式碼執行時在新增 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物件中,解鎖時會還原。
呼叫了物件的 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
當有其它執行緒使用偏向鎖物件時,會將偏向鎖升級為輕量級鎖
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 // 解鎖後處於不可偏向
這種情況下也會復原偏向鎖。因為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 // 重量級鎖
如果物件雖然被多個執行緒存取,但沒有競爭,這時偏向了執行緒 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
當復原偏向鎖閾值超過 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));
}
參考資料
@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
可以看到耗時有明顯差距了。
對相同物件多次加鎖,導致執行緒發生多次重入,可以使用鎖粗化方式來優化,這不同於之前的細分鎖的粒度。
本文來自部落格園,作者:|舊市拾荒|,轉載請註明原文連結:https://www.cnblogs.com/xiaoyh/p/17157540.html