Java的鎖升級策略

什麼是鎖?

java中,synchronized永遠都是鎖定的一個對象,那麼jvm是怎麼判斷一個對象是被鎖定的呢。java

java的對象內存分佈

Java的對象由對象頭,對象體和填充空間(Padding)組成。數組

  • 對象頭
    對象的描述信息
  • 實例數據
    對象的實際內容
  • 填充空間
    JVM要求對象的大小必須是8字節的整倍數,當實例數據不是8字節的整倍數時,須要填充空間來補上

Java對象頭的內容

  • Mark Word
    是對象頭的核心內容,記錄了對象的鎖信息
  • 類型指針
    指向對象類的元數據
  • 數組長度
    若是對象是數組,那還要有數據記錄數組的長度

Mark Word的組成

下圖爲32bit的JVM虛擬機中,Mark Word的組成:
markWord示意圖-e82a690527be4d30a5f8169a9ade87a0.jpg多線程

MarkWord經過標誌位來記錄對象當前的鎖信息。不一樣標示位的狀況下,Mark Word記錄的數據不同。jvm

鎖的區別

無鎖(01)

無鎖狀態下,對象不具有排他性,此時全部線程均可以直接訪問這個對象。性能

偏向鎖(01)

偏向鎖實際上就是無鎖,此時標記字記錄的是當前操做這個對象的線程ID。線程

輕量級鎖(00)

此時對象被鎖定,標記字指向持有當前對象的線程的地址。等待的線程會進入自旋狀態,經過CAS來爭搶鎖。指針

重量級鎖(10)

此時對象被鎖定,標記字指向當前對象的Monitor(對象監視器)的地址。等待的線程會進入阻塞狀態。對象

鎖的升級過程

Java的鎖只會升級,不會降級。當鎖所有被釋放後,會回到初始狀態,等待再次被升級。內存

初始狀態

JDK6開始,JVM默認打開了偏向鎖,所以默認狀況下,一個對象被建立時,對象頭中是偏向鎖的信息。虛擬機

  • 打開偏向鎖(默認)
    捕獲.PNG
  • 關閉偏向鎖
    捕獲.PNG

有一個線程使用synchronized鎖定了這個對象

此時JVM默認永遠只有這個線程使用這個對象,爲了減小性能消耗,會進入偏向鎖狀態,實際上並不會上鎖。
捕獲.PNG

另外一個線程嘗試經過synchronized獲取對象的鎖

當出現了鎖爭奪時,會升級爲輕量級鎖,此時兩個線程會嘗試修改標記字本身的線程地址,修改爲功的線程獲取到鎖,修改失敗的線程進入到自旋狀態,經過CAS操做來重試修改標記字。
捕獲.PNG

第二個線程自旋到了必定次數,或者出現了更多線程來爭奪鎖

此時JVM會把對象的鎖升級爲重量級鎖,標記字會指向對象的對象監視器(Monitor),全部爭奪失敗的線程進入阻塞狀態。
捕獲.PNG

重量級鎖的內部實現

什麼是對象監視器

每一個對象都有一個對應的對象監視器,用來控制對象的多線程訪問。

對象監視器的內部結構

  • Owner
    指向正持有對象鎖的線程
  • EntryList(鎖池)
    存放競爭失敗的線程
  • WaitSet(等待池)
    存放當前處於等待狀態的線程

對象監視器的流程

初始

多個線程競爭對象的鎖,爭奪成功的線程引用放入到Owner中,其餘線程放入到鎖池中

線程正常執行

執行完成後,調用monitorExit,釋放鎖。
此時從鎖池中按照先進先出原則,取出下一個線程,和新進的線程(若是有的話)來爭奪鎖。

調用了Object.wait()方法

調用wait()方法,線程進入掛起狀態,線程的引用會被放入到waitSet中,從鎖池中按照先進先出原則,取出下一個線程,和新進的線程(若是有的話)來爭奪鎖。

調用了Object.notify()方法

調用notify()方法後,從waitSet中拿到一個線程(具體拿哪一個取決於JVM的配置),當即去爭奪對象的鎖,若是失敗則進入到鎖池。

調用了Object.notifyAll()方法

調用notify()方法後,從waitSet中拿出全部線程,當即去爭奪對象的鎖,若是失敗則進入到鎖池。
除非明確知道只有一個線程出於wait狀態,不然就是用notifyAll方法,防止線程等待過久或永遠等下去。

相關文章
相關標籤/搜索