作者:京東零售 劉躍明
Monitor概念
Java物件的記憶體布局
物件除了我們自定義的一些屬性外,還有其它資料,在記憶體中可以分為三個區域:物件頭、實體資料、對齊填充,這三個區域組成起來才是一個完整的物件,
物件頭:在JVM中需要大量存盤物件,存盤時為了實作一些額外的功能,需要在物件中添加一些標記欄位用于增強物件功能,這些標記欄位組成了物件頭,
實體資料:存放類的屬性資料資訊,包括父類的屬性資訊,
對齊填充:由于虛擬機要求物件其實地址必須是8位元組的整數倍,需要存在填充區域以滿足8位元組的整數倍,填充資料不是必須存在的,僅僅是為了位元組對齊,
圖1
Java物件頭
JVM中物件頭的方式有以下兩種(以32位虛擬機為例):
普通物件
| Object Header (64 bits) | |
|---|---|
| Mark Word (32 bits) | Klass Word (32 bits) |
陣列物件
| Object Header (96 bits) | ||
|---|---|---|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
Mark Word
這部分主要用來存盤物件自身的運行資料,如hashcode、gc分帶年齡等,Mark Word的位長度為JVM的一個Word大小,也就是說32位JVM的Mark Word為32位,64位JVM為64位,為了讓一個字大小存盤更多的資訊,JVM將字的最低兩個位設定為標記位,不同標記位下的Mark Word示意如下:
| Mark Word (32 bits) | State | ||||
|---|---|---|---|---|---|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal | |
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
| ptr_to_lock_record:30 | lock:2 | LightweightLocked | |||
| ptr_to_heavyweight_monitor:30 | lock:2 | HeavyweightLocked | |||
| lock:2 | Marked for GC |
其中各部分的含義如下:
lock: 2位的鎖狀態標記位,該標記的值不同,整個Mark Word表示的含義不同,
| biased_lock | lock | 狀態 |
|---|---|---|
| 0 | 01 | 無鎖 |
| 1 | 01 | 偏向鎖 |
| 0 | 00 | 輕量級鎖 |
| 0 | 10 | 重量級鎖 |
| 0 | 11 | GC標記 |
biased_lock: 物件是否啟用偏向鎖標記,只占1個二進制位,為1時表示物件啟用偏向鎖,為0時表示物件沒有偏向鎖,
age: 4位的Java物件年齡,在GC中,如果物件再Survivor區復制一次,年齡增加1,當物件達到設定的閾值時,將會晉升到老年代,默認情況下,并行GC的年齡閾值為15,并發GC的年齡閾值為6,由于age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因,
identity_hashcode: 25位的物件表示Hash碼,采用延遲加載技術,呼叫方法System.idenHashcode()計算,并會將結果寫到該物件頭中,當物件被鎖定時,該值會移動到管程Monitor中,
thread: 持有偏向鎖的執行緒ID,
epoch: 偏向時間戳,
ptr_to_lock_record: 指向堆疊中鎖記錄的指標,
ptr_to_heavyweight_monitor: 指向管程Monitor的指標,
Klass Word
這一部分用于存盤物件的型別指標,該指標指向它的類元資料,JVM通過這個指標確定物件是哪個類的實體,該指標的位長度為JVM的一個字大小,即32位的JVM為32位,64位的JVM為64位,
array length
如果物件是一個陣列,那么物件頭還需要有額外的空間用于存盤陣列的長度,這部分資料的長度也隨著JVM架構的不同而不同:32位的JVM長度為32位,64位JVM則為64位,
Monitor原理
Monitor被翻譯為監視器或管程
每個Java物件都可以關聯一個Monitor物件,如果使用synchronized給物件上鎖(重量級)之后,該物件頭的Mark Word中就被設定指向Monitor物件的指標,
Monitor結構如下:
圖2
?剛開始Monitor中Owner為null
?當Thread-2執行synchronized(obj)就會將Monitor的所有者Owner置為Thread-2,Monitor中只能有一個Owner
?在Thread-2上鎖的程序中,如果Thread-3、Thread-4、Thread-5也來執行synchronized(obj),就會進入EntryList BLOCKED
?Thread-2執行完同步代碼塊的內容,然后喚醒EntryList中等待的執行緒來競爭鎖,競爭是非公平的,也就是先進并非先獲取鎖
?圖2中WaitSet中的Thread-0、Thread-1是之前獲得過鎖,但條件不滿足進入WAITING狀態的執行緒,后面講wait-notify時會分析
注意:
?synchronized必須是進入同一個物件的Monitor才有上述的效果
?不加synchronized的物件不會關聯監視器,不遵從以上規則
synchronized原理
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
對應的位元組碼為:
public static main([Ljava/lang/String;)V
TRYCATCHBLOCK L0 L1 L2 null
TRYCATCHBLOCK L2 L3 L2 null
L4
LINENUMBER 6 L4
GETSTATIC MyClass03.lock : Ljava/lang/Object;
DUP
ASTORE 1
MONITORENTER //注釋1
L0
LINENUMBER 7 L0
GETSTATIC MyClass03.counter : I
ICONST_1
IADD
PUTSTATIC MyClass03.counter : I
L5
LINENUMBER 8 L5
ALOAD 1
MONITOREXIT //注釋2
L1
GOTO L6
L2
FRAME FULL [[Ljava/lang/String; java/lang/Object] [java/lang/Throwable]
ASTORE 2
ALOAD 1
MONITOREXIT //注釋3
L3
ALOAD 2
ATHROW
L6
LINENUMBER 9 L6
FRAME CHOP 1
RETURN
L7
LOCALVARIABLE args [Ljava/lang/String; L4 L7 0
MAXSTACK = 2
MAXLOCALS = 3
注釋1
MONITORENTER的意思為:每個物件都有一個監視鎖(Monitor),當Monitor被占用時就會處于鎖定狀態,執行緒執行MONITORENTER指令時嘗試獲取Monitor的所有權,程序如下:
?如果Monitor的進入數為0,則該執行緒進入Monitor,并將進入數設定為1,該執行緒即為Monitor的所有者(Owner)
?如果該執行緒已經占用Monitor,只是重新進入Monitor,則進入Monitor的進入數加1
?如果其它執行緒已經占用Monitor,則該執行緒進入阻塞狀態,直到Monitor進入數為0,再重新嘗試獲取Monitor的所有權
注釋2
MONITOREXIT的意思為:執行指令時,Monitor的進入數減1,如果減1后進入數為0,該執行緒退出Monitor,不再是這個Monitor的所有者,其它被Monitor阻塞的執行緒重新嘗試獲取Monitor的所有權,
總結
通過注釋1和注釋2可知,synchronized的實作原理,底層是通過Monitor的物件來完成,其實wait和notify等方法也依賴Monitor,這就是為什么wait和notify方法必須要在同步方法內呼叫,否則會拋出java.lang.IllegalMonitorStateException的原因,
如果程式正常執行則按上述描述即可完成,如果程式在同步方法內發生例外,代碼則會走注釋3,在注釋3可以看到MONITOREXIT指令,也就是synchronized已經處理例外情況下的退出,
注:方法級別的synchronized不會在位元組碼指令中有所體現,而是在常量池中增加了ACC_SYNCHRONIZED識別符號,JVM就是通過該識別符號來實作同步的,方法呼叫時,JVM會判斷方法的ACC_SYNCHRONIZED是否被設定,如果被設定,執行緒執行方法前會先獲取Monitor所有權,執行完方法后再釋放Monitor所有權,本質是一樣的,
synchronized原理進階
輕量級鎖
輕量級鎖的使用場景:如果一個物件雖然有多執行緒要加鎖,但加鎖的時間是錯開的(也就是沒有競爭),那么可以使用輕量級鎖來優化,
輕量級鎖對使用者是透明的,即語法仍然是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
圖3
讓鎖記錄中Object reference指向鎖物件,并嘗試用cas替換Object的Mark Word,將Mark Word的值存入鎖記錄
圖4
如果cas替換成功,物件頭中存盤了鎖記錄地址和狀態00,表示由該執行緒給物件加鎖,這是圖示如下
圖5
如果cas失敗,有兩種情況
?如果是其它執行緒已經持有了該Object的輕量級鎖,這是表明有競爭,進入鎖膨脹程序
?如果是自己執行緒執行了synchronized鎖重入,那么再添加一條Lock Record作為重入的技術
圖6
當退出synchronized代碼塊(解鎖時),如果有取值為null的鎖記錄,表示由重入,這是重置鎖記錄,表示重入技術減一
圖7
當退出synchronized代碼塊(解鎖時),鎖記錄的值不為null,這時使用cas將Mark Word的值回復給物件頭
?成功,則解鎖成功
?失敗,說明輕量級鎖進行了鎖膨脹或已經升級為重量級鎖,進入重量級鎖解鎖流程
鎖膨脹
如果在嘗試加輕量級鎖的程序中,CAS操作無法成功,這是一種情況就是有其它執行緒為此物件加上了輕量級鎖(有競爭),這是需要進行鎖膨脹,將輕量級鎖變為重量級鎖,
當Thread-1進行輕量級加鎖時,Thread-0已經對該物件加了輕量級鎖
圖8
這是Thread-1加輕量級鎖失敗,進入鎖膨脹流程
?即為Object物件申請Monitor鎖,讓Object指向重量級鎖地址
?然后自己進入Monitor的EntryList BLOCKED
圖9
當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(重量鎖)重量鎖指標 | 執行同步塊 |
| - | … | … |
自旋重試失敗的情況
| 執行緒1(core 1上) | 物件Mark | 執行緒2(core 2 上) |
|---|---|---|
| - | 10(重量鎖) | - |
| 訪問同步塊,獲取Monitor | 10(重量鎖)重量鎖指標 | - |
| 成功(加鎖) | 10(重量鎖)重量鎖指標 | - |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | - |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | 訪問同步塊,獲取Monitor |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | 自旋重試 |
| 執行同步塊 | 10(重量鎖)重量鎖指標 | 阻塞 |
| - | … | … |
?自旋會占用CPU時間,單核CPU自旋就是浪費,多核CPU自旋才能發揮優勢,
?在Java 6之后自旋鎖是自適應的,比如物件剛剛的一次自旋操作成功過,那么認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智能,
?Java 7之后不能控制是否開啟自旋功能,
偏向鎖
輕量級鎖在沒有競爭時(就自己這個執行緒),每次重入仍然需要執行CAS操作,
Java 6中引入了偏向鎖做進一步優化:只有第一次使用CAS將執行緒ID設定到物件的Mark Word頭,之后發現這個執行緒ID是自己的就表示沒有競爭,不用重新CAS,以后只要不發生競爭,這個物件就歸該執行緒所有,
注:
Java 15之后廢棄偏向鎖,默認是關閉,如果想使用偏向鎖,配置-XX:+UseBiasedLocking啟動引數,
啟動偏向鎖之后,偏向鎖有一個延遲生效的機制,這是因為JVM啟動時會進行一系列的復雜活動,比如裝載配置,系統類初始化等等,在這個程序中會使用大量synchronized關鍵字對物件加鎖,且這些鎖大多數都不是偏向鎖,為了減少初始化時間,JVM默認延時加載偏向鎖,這個延時的時間大概為4s左右,具體時間因機器而異,當然我們也可以設定JVM引數 -XX:BiasedLockingStartupDelay=0 來取消延時加載偏向鎖,
例如:
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) {
}
}
如果關閉偏向鎖,使用輕量鎖情況:
圖10
開啟偏向鎖,使用偏向鎖情況:
圖11
偏向狀態
回憶一下物件頭格式
| Mark Word (32 bits) | State | ||||
|---|---|---|---|---|---|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal | |
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
| ptr_to_lock_record:30 | lock:2 | LightweightLocked | |||
| ptr_to_heavyweight_monitor:30 | lock:2 | HeavyweightLocked | |||
| lock:2 | Marked for GC |
一個物件創建時:
?如果開啟了偏向鎖(默認開啟),那么物件創建后,Mark Word值為0x05,也就是最后是3位為101,這是它的thread、epoch、age都為0
?如果沒有開啟偏向鎖,那么物件創建后,Mark Word值為0x01,也就是最后3位為001,這時它的hashcode、age都為0,第一次用到hashcode時才會賦值
我們來驗證下,使用jol第三方工具,以及對工具列印物件頭做了一個處理,讓物件頭開起來更簡便:
測驗代碼
public synchronized static void main(String[] args){
log.info("{}", toSimplePrintable(object));
}
開啟偏向鎖的情況下
列印的資料如下(由于Java15之后偏向鎖廢棄,因此打開偏向鎖列印會警告)
17:15:17 [main] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
最后為101,其他都為0,驗證了上述第一條,
可能你又要問了,我這也沒使用synchronized關鍵字呀,那不也應該是無鎖么?怎么會是偏向鎖呢?
仔細看一下偏向鎖的組成,對照輸出結果紅色劃線位置,你會發現占用 thread 和 epoch 的 位置的均為0,說明當前偏向鎖并沒有偏向任何執行緒,此時這個偏向鎖正處于可偏向狀態,準備好進行偏向了!你也可以理解為此時的偏向鎖是一個特殊狀態的無鎖,
關閉偏向鎖的情況下
列印的資料如下
17:18:32 [main] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
最后為001,其它都是0,驗證了上述第二條,
接下來驗證加鎖的情況,代碼如下:
private static Object object = new Object();
public synchronized static void main(String[] args){
new Thread(()->{
log.info("{}", "synchronized前");
log.info("{}", toSimplePrintable(object));
synchronized (object){
log.info("{}", "synchronized中");
log.info("{}", toSimplePrintable(object));
}
log.info("{}", "synchronized后");
log.info("{}", toSimplePrintable(object));
},"t1").start();
}
開啟偏向鎖的情況,列印資料如下
17:24:05 [t1] c.MyClass03 - synchronized前
17:24:05 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
17:24:05 [t1] c.MyClass03 - synchronized中
17:24:05 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 00001110 00000111 01001000 00000101
17:24:05 [t1] c.MyClass03 - synchronized后
17:24:05 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 00001110 00000111 01001000 00000101
使用了偏向鎖,并記錄了執行緒的值(101前面的一串數字),但是處于偏向鎖的物件解鎖后,執行緒id仍存盤于物件頭中,
關閉偏向鎖的情況,列印資料如下
17:28:24 [t1] c.MyClass03 - synchronized前
17:28:24 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
17:28:24 [t1] c.MyClass03 - synchronized中
17:28:24 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 01110000 00100100 10101001 01100000
17:28:24 [t1] c.MyClass03 - synchronized后
17:28:24 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
使用輕量鎖(最后為000),并且記錄了占中存盤的鎖資訊地址(000前面一串數字),同步塊結束后恢復到原先狀態(因為沒有使用hashcode,所以hashcode值為0),
偏向鎖撤銷
在真正講解偏向撤銷之前,需要和大家明確一個概念——偏向鎖撤銷和偏向鎖釋放是兩碼事,
?撤銷:籠統的說就是多個執行緒競爭導致不能再使用偏向模式的時候,主要是告知這個鎖物件不能再用偏向模式
?釋放:和你的常規理解一樣,對應的就是 synchronized 方法的退出或 synchronized 塊的結束
何為偏向撤銷?
從偏向狀態撤回原有的狀態,也就是將 MarkWord 的第 3 位(是否偏向撤銷)的值,從 1 變回 0
如果只是一個執行緒獲取鎖,再加上「偏心」的機制,是沒有理由撤銷偏向的,所以偏向的撤銷只能發生在有競爭的情況下
撤銷-hashcode呼叫
呼叫了物件的hashcode會導致偏向鎖被撤銷:
?輕量級鎖會在鎖記錄中記錄hashcode
?重量級鎖會在Monitor中記錄hashcode
測驗代碼如下
private static Object object = new Object();
public synchronized static void main(String[] args){
object.hashCode();//呼叫hashcode
new Thread(()->{
log.info("{}", "synchronized前");
log.info("{}", toSimplePrintable(object));
synchronized (object){
log.info("{}", "synchronized中");
log.info("{}", toSimplePrintable(object));
}
log.info("{}", "synchronized后");
log.info("{}", toSimplePrintable(object));
},"t1").start();
}
列印如下:
17:36:05 [t1] c.MyClass03 - synchronized前
17:36:06 [t1] c.MyClass03 - 00000000 00000000 00000000 01011111 00100001 00001000 10110101 00000001
17:36:06 [t1] c.MyClass03 - synchronized中
17:36:06 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 01101110 00010011 11101001 01100000
17:36:06 [t1] c.MyClass03 - synchronized后
17:36:06 [t1] c.MyClass03 - 00000000 00000000 00000000 01011111 00100001 00001000 10110101 00000001
撤銷-其它執行緒使用物件
當有其它執行緒使用偏向鎖物件時,會將偏向鎖升級為輕量級鎖,
測驗代碼如下
private static void test2() {
Thread t1 = new Thread(() -> {
synchronized (object) {
log.info("{}", toSimplePrintable(object));
}
synchronized (MyClass03.class) {
MyClass03.class.notify();//t1執行完之后才通知t2執行
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (MyClass03.class) {
try {
MyClass03.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("{}", toSimplePrintable(object));
synchronized (object) {
log.info("{}", toSimplePrintable(object));
}
log.info("{}", toSimplePrintable(object));
}, "t2");
t2.start();
}
列印資料如下
17:51:38 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 01000111 00000000 11101000 00000101
17:51:38 [t2] c.MyClass03 - 00000000 00000000 00000000 00000001 01000111 00000000 11101000 00000101
17:51:38 [t2] c.MyClass03 - 00000000 00000000 00000000 00000001 01111000 00100000 01101001 01010000
17:51:38 [t2] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
可以看到執行緒t1是使用偏向鎖,執行緒t2使用鎖之前是一樣的,但是一旦使用了鎖,便升級為輕量級鎖,執行完同步代碼之后,恢復成撤銷偏向鎖的狀態,
撤銷-呼叫wait/notify
代碼如下
private static void test3(){
Thread t1 = new Thread(() -> {
log.info("{}", toSimplePrintable(object));
synchronized (object) {
log.info("{}", toSimplePrintable(object));
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("{}", toSimplePrintable(object));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
log.debug("notify");
object.notify();
}
}, "t2").start();
}
列印資料如下
17:57:57 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
17:57:57 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 00001111 00001100 11010000 00000101
17:58:02 [t2] c.MyClass03 - notify
17:58:02 [t1] c.MyClass03 - 00000000 00000000 01100000 00000000 00000011 11000001 10000010 01110010
呼叫wait和notify得是用Monitor,所以會從偏向鎖升級為重量級鎖,
批量重偏向
如果物件雖然被多個執行緒訪問,但沒有競爭,這是偏向了執行緒t1的物件仍然有機會重新偏向t2,重偏向會重置物件的Thread ID,
當撤銷偏向鎖閾值超過20次后,JVM會這樣覺得,我是不是偏向錯了呢,于是會在給這些物件加鎖時重新偏向至加鎖執行緒,
代碼如下
public static class Dog{}
private static void test4() {
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.info("{}", i+"\t"+toSimplePrintable(d));
}
}
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.info("{}", i+"\t"+toSimplePrintable(d));
synchronized (d) {
log.info("{}", i+"\t"+toSimplePrintable(d));
}
log.info("{}", i+"\t"+toSimplePrintable(d));
}
}, "t2");
t2.start();
}
列印如下
圖12
另外我在測驗的是否發現一個執行緒,當物件是普通類(如Dog)時,重偏向的閾值就是20,也就是第21次開啟了偏向鎖,但是如果把普通類替換成Object時,重偏向的閾值就是9,也就是第10次開啟了偏向鎖并重偏向(如圖13),這是怎么回事兒,有了解的同學可以評論交流下,
圖13
批量撤銷
當撤銷偏向鎖閾值超過40次后,JVM會這樣覺得,自己確實偏向錯了,根本不該偏向,于是整個類的所有物件都會變為不可偏向的,新建的物件也是不可偏向的,
代碼如下
static Thread t1, t2, t3;
private static void test6() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 40;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.info("{}", i + "\t" + toSimplePrintable(d));
}
}
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.info("{}", i + "\t" + toSimplePrintable(d));
synchronized (d) {
log.info("{}", i + "\t" + toSimplePrintable(d));
}
log.info("{}", i + "\t" + toSimplePrintable(d));
}
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.info("{}", i + "\t" + toSimplePrintable(d));
synchronized (d) {
log.info("{}", i + "\t" + toSimplePrintable(d));
}
log.info("{}", i + "\t" + toSimplePrintable(d));
}
}, "t3");
t3.start();
t3.join();
log.info("{}", toSimplePrintable(new Dog()));
}
列印如下
圖14
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543848.html
標籤:Java
下一篇:Java-類加載器
