1. Synchronized的各種使用方式 以及 鎖的都是什么:
Synchronized方法,且為非靜態(tài)方法:鎖的是當前類的實例對象(this)。
Synchronized方法,但是為靜態(tài)方法:鎖的是當前類對象。
Synchronized塊(Object):鎖的是Object對象。
Synchronized塊(this):鎖的是當前類的實例對象。
Synchronized塊(類.class):鎖的是當前類對象。
2. 從字節(jié)碼看Synchronized的實現原理:monitor
2.1 synchronized方法經過編譯生成的字節(jié)碼:
可以看到synchronized方法的flags屬性為ACC_SYNCHRONIZED
synchronized void hy () { }
synchronized void hy(); descriptor: ()V flags: ACC_SYNCHRONIZED Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 5: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this LTest27JavapSycnhronized;
2.2 synchronized塊經過編譯生成的字節(jié)碼:
可以看到synchronized塊所在的方法的Code區(qū)多了一對monitorenter和monitorexit命令:
void hy6 (Hy h) { synchronized (h) { doSomething(); }
1 void hy6(Hy); 2 descriptor: (LHy;)V 3 flags: 4 Code: 5 stack=2, locals=4, args_size=2 6 0: aload_1 //將對象h從局部變量表入操作數棧(局部變量表的_1=對象h) 8 1: dup // 將棧頂的對象h復制并再入棧 9 2: astore_2 // 將對象h(新復制來的)加載入局部變量表,意味這是新元素(棧中的新h ——>成為 局部變量表的_2) 12 3: monitorenter // 鎖住當前棧頂元素(即老對象h)的monitor, 13 4: aload_0 // 將局部變量表中的this入棧,目的是下一步執(zhí)行dosomething方法時可以從棧頂取到this,因為this是dosomething方法 的入參。(局部變量表的_0=this) 18 5: invokevirtual #4 // 調用常量池的#4 ,即dosomething方法 19 8: aload_2 // 將局部變量表中的新h入棧(局部變量表的_2=新h) 21 9: monitorexit // 退出(即老對象h)的monitor, 22 10: goto 18 // 既然已經退出,說明是正常結束,取到#18行return掉。 24 13: astore_3 // 走到這里13行說明已經是異常了,因為查看異常表 Exception table得知,4~10發(fā)生的異常會走到13行(棧中的異常對象 ——>成為 局部變量表的_3) 28 14: aload_2 // 將局部變量表中的新h入棧(局部變量表的_2=新h) 30 15: monitorexit // 退出(即老對象h)的monitor, 31 16: aload_3 // 將局部變量表中的異常對象入棧(局部變量表的_3=異常對象) 33 17: athrow // 拋出異常 34 18: return // 返回 35 36 Exception table: 37 from to target type 38 4 10 13 any 39 13 16 13 any
2.3 總結一下
分別寫一個Synchronized方法和Synchronized塊,觀察javap反編譯后的字節(jié)碼:
Synchronized方法(不論靜態(tài)非靜態(tài))利用的機制是:
在所在方法的flags屬性會多了一個參數:ACC_SYNCHRONIZED。
Synchronized塊(鎖的不論對象還是class)利用的機制是:
在所在方法的Code域中多了兩個動作:monitorenter和monitorexit。
2.4 ACC_SYNCHRONIZED 和 monitorenter&monitorexit 有什么區(qū)別:
其實兩者的底層實現是一樣的,只不過ACC_SYNCHRONIZED是隱式的。
1. ACC_SYNCHRONIZED:
當虛擬機執(zhí)行該方法時,看到該方法的flags之一是ACC_SYNCHRONIZED,
就會要求當前線程嘗試獲取當前操作數棧頂元素的monitor(如果flags還有ACC_STATIC,就獲取當前調用者類對象的monitor),然后才能執(zhí)行方法。
2. monitorenter&monitorexit:
執(zhí)行monitorenter字節(jié)碼指令時,當前線程嘗試獲取當前操作數棧頂元素的monitor,
執(zhí)行monitorexit字節(jié)碼指令時,退出那個元素的monitor。
3. Monitor
3.1 Monitor簡介
每個對象有一個monitor監(jiān)視器。
monitor可以實現Synchronized、wait / notify。
這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
執(zhí)行monitorenter時,當前線程嘗試獲取當前操作數棧頂元素的monitor:
1、如果發(fā)現此時monitor的進入數(_count字段)為0,則該線程成功進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
2、如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1.
3、如果發(fā)現此時其他線程已經占用了monitor,則該線程進入阻塞狀態(tài)。
執(zhí)行monitorexit時,當前線程嘗試退出那個元素的monitor,(前提是當前線程必須是那個元素的monitor所有者)
1、monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。
其他被這個monitor阻塞的線程(monitor的_EntryList字段)可以嘗試去獲取這個 monitor 的所有權。
3.2 Monitor詳細介紹
ObjectMonitor中有幾個關鍵屬性:
_owner:指向持有ObjectMonitor對象的線程
_recursions:鎖的重入次數
_count:用來記錄該線程獲取鎖的次數
_WaitSet:如果一個線程在同步塊中調用了wait方法,會將該線程加入到WaitSet中,然后釋放鎖。當wait的線程被notify之后,會將對應的線程從WaitSet移動到EntryList中
_cxq(ContentionList):當一個線程嘗試獲得鎖時,如果該鎖已經被占用,則會將該線程插入到cxq隊列的隊首
_EntryList:當持有鎖的線程釋放鎖前,會將cxq中的所有元素移動到EntryList中去,并喚醒EntryList的隊首線程
3.2.1 以Synchronized場景舉例說明monitor的線程阻塞順序和策略
當一個線程嘗試獲得鎖時,如果該鎖已經被占用,則會將該線程插入到cxq隊列的隊首
3.2.2 以Synchronized場景舉例說明monitor的線程喚醒順序和策略
當一個線程執(zhí)行到monitorexit時,會執(zhí)行喚醒策略:
如果QMode = 2的操作最特殊:取_cxq隊列首元素喚醒;
如果QMode = 3,把_cxq隊列的首元素放入_EntryList尾部(相當于順序),然后執(zhí)行步驟5;
如果QMode = 4,把_cxq隊列的首元素放入_EntryList頭部(相當于倒序),然后執(zhí)行步驟5;
如果QMode = 0(默認),不做什么,執(zhí)行步驟下一步:
如果_EntryList非空,就取首元素+調用ExitEpilog方法,該方法會喚醒該元素對應的線程,然后立即返回;
如果_EntryList為空,會將cxq中的所有元素移動到EntryList中去+調用ExitEpilog方法,喚醒EntryList的隊首線程,該方法會喚醒該元素對應的線程,然后立即返回;
假設線程A、B、C的執(zhí)行順序為 A->B->C,并且A線程首先搶到了鎖:
因為鎖被A線程占用,則會將B、C線程插入到cxq隊列中,由于每次都是插入的隊首,所以cxq隊列中的元素順序為C->B,
又因為EntryList為空,所以當A線程執(zhí)行完畢后,則會取出C線程進行執(zhí)行。
3.2.3 monitor實現wait/notify應該和實現Synchronized的方式差不多,只不過cxq隊列換成_WaitSet隊列而已
略。

浙公網安備 33010602011771號