Java鎖升級過程

markword實現表:

  Java對象頭裏的markword主要存儲虛擬機有關該對象的運行數據,如hashcode、gc年齡和鎖信息,實現表如下圖所示:
在這裏插入圖片描述  markword中最低的三位代表鎖狀態 其中1位是偏向鎖位 兩位是普通鎖位。

鎖升級過程:

  1. 當一個Java對象被new出來,markword最低的三位爲:001,表示無鎖態;
Obiect o = new Object();
  1. 當對象獲取自身的hashcode時,markword會用31位來存儲hashcode,markword最低的三位爲:001,表示無鎖態;
o.hashcode();
  1. 當對象被synchronized關鍵字修飾時,默認是輕量級鎖。偏向鎖有個時延,默認是4秒,因爲JVM虛擬機自己有一些默認啓動的線程,裏面有好多synchronized代碼,這些synchronized代碼啓動時就知道肯定會有競爭,如果使用偏向鎖,就會造成偏向鎖不斷的進行鎖撤銷和鎖升級的操作,效率較低。
synchronized(o){
    // TODO
}
  1. 可以設置“-XX:+UseBiasedLocking”和“-XX:BiasedLockingStartupDelay=0”來打開偏向鎖並設置偏向鎖啓動延遲爲0,此時new出來的對象默認就是可偏向匿名對象,markword最低的三位爲:101,線程ID爲0;
-XX:+useBiasedLocking
-XX:BiasedLockingStartupDelay=0
  1. 如果有線程上鎖,上偏向鎖指的就是,把markword的線程ID改爲自己線程ID的過程,偏向鎖不可重偏向、批量偏向、批量撤銷。下次同一個線程加鎖的時候,不需要爭用,只需要判斷線程指針是否同一個,所以,偏向鎖偏向加鎖的第一個線程,hashCode備份在線程棧上,線程銷燬,鎖降級爲無鎖。偏向鎖由於有鎖撤銷的過程revoke,會消耗系統資源,所以,在鎖爭用特別激烈的時候,用偏向鎖未必效率高。還不如直接使用輕量級鎖;
  2. 如果有線程競爭,撤銷偏向鎖,升級輕量級鎖,線程在自己的線程棧生成LockRecord,用CAS操作將markword設置爲指向自己這個線程的LockRecord的指針,設置成功者得到鎖;自旋鎖在JDK1.4.2 中引入,使用“-XX:+UseSpinning”來開啓。JDK 6 中變爲默認開啓,並且引入了自適應的自旋鎖(適應性自旋鎖),JVM自己控制。
  3. 自適應自旋鎖意味着自旋的時間(次數)不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,並且持有鎖的線程正在運行中,那麼虛擬機就會認爲這次自旋也是很有可能再次成功,進而它將允許自旋等待持續相對更長的時間。如果對於某個鎖,自旋很少成功獲得過,那在以後嘗試獲取這個鎖時將可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源;
  4. 如果競爭加劇:有線程超過10次自旋(通過“-XX:PreBlockSpin”設置自旋次數)或者自旋線程數超過CPU核數的一半,升級重量級鎖需要向操作系統申請資源(互斥鎖,linux mutex),CPU從3級->0級系統調用,線程掛起,進入等待隊列,等待操作系統的調度,然後再映射回用戶空間,用戶態與內核態的切換需要消耗資源。
注:JDK11,偏向鎖默認是打開的,但是有一個時延,如果要觀察到偏向鎖,應該設定參數,而JDK8默認對象頭是無鎖。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章