盤一盤 synchronized (二)—— 偏向鎖批量重偏向與批量撤銷

在本文講解以前,先來簡單瞭解一下爲何會有批量重偏向和批量撤銷。
 
批量重偏向:當一個線程建立了大量對象並執行了初始的同步操做,後來另外一個線程也來將這些對象做爲鎖對象進行操做,會導偏向鎖重偏向的操做。
批量撤銷:在多線程競爭劇烈的狀況下,使用偏向鎖將會下降效率,因而乎產生了批量撤銷機制。
 

 JVM的默認參數值

經過JVM的默認參數值,找一找批量重偏向和批量撤銷的閾值。
設置JVM參數-XX:+PrintFlagsFinal,在項目啓動時便可輸出JVM的默認參數值
 
intx BiasedLockingBulkRebiasThreshold   = 20   默認偏向鎖批量重偏向閾值
intx BiasedLockingBulkRevokeThreshold  = 40   默認偏向鎖批量撤銷閾值
固然咱們能夠經過-XX:BiasedLockingBulkRebiasThreshold 和 -XX:BiasedLockingBulkRevokeThreshold 來手動設置閾值

 

批量重偏向

public static void main(String[] args) throws Exception { //延時產生可偏向對象 Thread.sleep(5000); //創造100個偏向線程t1的偏向鎖 List<A> listA = new ArrayList<>(); Thread t1 = new Thread(() -> { for (int i = 0; i <100 ; i++) { A a = new A(); synchronized (a){ listA.add(a); } } try { //爲了防止JVM線程複用,在建立完對象後,保持線程t1狀態爲存活 Thread.sleep(100000000); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); //睡眠3s鍾保證線程t1建立對象完成 Thread.sleep(3000); out.println("打印t1線程,list中第20個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(19)).toPrintable())); //建立線程t2競爭線程t1中已經退出同步塊的鎖 Thread t2 = new Thread(() -> { //這裏面只循環了30次!!! for (int i = 0; i < 30; i++) { A a =listA.get(i); synchronized (a){ //分別打印第19次和第20次偏向鎖重偏向結果 if(i==18||i==19){ out.println("第"+ ( i + 1) + "次偏向結果"); out.println((ClassLayout.parseInstance(a).toPrintable())); } } } try { Thread.sleep(10000000); } catch (InterruptedException e) { e.printStackTrace(); } }); t2.start(); Thread.sleep(3000); out.println("打印list中第11個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(10)).toPrintable())); out.println("打印list中第26個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(25)).toPrintable())); out.println("打印list中第41個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(40)).toPrintable())); }
 
咱們一步一步來分析打印結果
首先,創造了100個偏向線程t1的偏向鎖,結果沒什麼好說的,100個偏向鎖嘛,偏向的線程ID信息爲531939333

 

再來看看重偏向的結果,線程t2,前19次偏向均產生了輕量鎖,而到第20次的時候,達到了批量重偏向的閾值20,此時鎖並非輕量級鎖,而變成了偏向鎖,此時偏向的線程t2,線程t2的ID信息爲5322162821多線程

 

最後咱們再來看一下偏向結束後的對象頭信息。
前20個對象,並無觸發了批量重偏向機制,線程t2執行釋放同步鎖後,轉變爲無鎖形態
第20~30個對象,觸發了批量重偏向機制,對象爲偏向鎖狀態,偏向線程t2,線程t2的ID信息爲5322162821
而31個對象以後,也沒有觸發了批量重偏向機制,對象仍偏向線程t1,線程t1的ID信息爲531939333

 

批量撤銷

 
咱們再來看看批量撤銷
public static void main(String[] args) throws Exception { Thread.sleep(5000); List<A> listA = new ArrayList<>(); Thread t1 = new Thread(() -> { for (int i = 0; i <100 ; i++) { A a = new A(); synchronized (a){ listA.add(a); } } try { Thread.sleep(100000000); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); Thread.sleep(3000); Thread t2 = new Thread(() -> { //這裏循環了40次。達到了批量撤銷的閾值 for (int i = 0; i < 40; i++) { A a =listA.get(i); synchronized (a){ } } try { Thread.sleep(10000000); } catch (InterruptedException e) { e.printStackTrace(); } }); t2.start(); //———————————分割線,前面代碼再也不贅述—————————————————————————————————————————— Thread.sleep(3000); out.println("打印list中第11個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(10)).toPrintable())); out.println("打印list中第26個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(25)).toPrintable())); out.println("打印list中第90個對象的對象頭:"); out.println((ClassLayout.parseInstance(listA.get(89)).toPrintable())); Thread t3 = new Thread(() -> { for (int i = 20; i < 40; i++) { A a =listA.get(i); synchronized (a){ if(i==20||i==22){ out.println("thread3 第"+ i + "次"); out.println((ClassLayout.parseInstance(a).toPrintable())); } } } }); t3.start(); Thread.sleep(10000); out.println("從新輸出新實例A"); out.println((ClassLayout.parseInstance(new A()).toPrintable())); }
 
來看看輸出結果,這部分和上面批量偏向結果的截然不同。重點關注記錄的線程ID信息
前20個對象,並無觸發了批量重偏向機制,線程t2執行釋放同步鎖後,轉變爲無鎖形態
第20~40個對象,觸發了批量重偏向機制,對象爲偏向鎖狀態,偏向線程t2,線程t2的ID信息爲540039429
而41個對象以後,也沒有觸發了批量重偏向機制,對象仍偏向線程t1,線程t1的ID信息爲540002309

 

 
重頭戲來了!線程t3也來競爭鎖。由於已經達到了批量撤銷的閾值,且對象listA.get(20)和listA.get(22)已經進行過偏向鎖的重偏向,並不會再次重偏向線程t3。
此時觸發批量撤銷,此時對象鎖膨脹變爲輕量級鎖。
 

 

再來看看最後新生成的對象A。值得注意的是:本應該爲可偏向狀態偏向鎖的新對象,在經歷過批量重偏向和批量撤銷後直接在實例化後轉爲無鎖。優化

 

簡單總結

一、批量重偏向和批量撤銷是針對類的優化,和對象無關。
二、偏向鎖重偏向一次以後不可再次重偏向。
三、當某個類已經觸發批量撤銷機制後,JVM會默認當前類產生了嚴重的問題,剝奪了該類的新實例對象使用偏向鎖的權利
相關文章
相關標籤/搜索