天天看點

簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖

全文使用synchronized來說明。

synchronized給對象上鎖,先上偏向鎖,在上輕量級鎖,最後上重量級鎖。上什麼鎖,是gvm根據競争程度自行變換的。

重量級鎖

計算機作業系統本有Monitor對象,稱為管程。在java裡面看不到此對象。

每個Java對象都可以關聯一個monitor對象,如果使用了synchronized給對象上重量級鎖後,該對象的Mark word就被設定指向monitor對象的指針。

Monitor對象結構

簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖
  • WaitSet:是線程等待隊列。狀态為WAITING
  • EntryList:線程阻塞隊列.狀态為BLOCKED
  • Owner:正在執行的線程(可能很多線程競争一個資源,但隻有一個線程能夠成功,此時Owner就置為此線程)

原理

  1. 建立的對象,此時Mark Word關聯一個Monitor對象(即Mark Word記錄一個monitor對象的位址)。此時Monitor對象裡面的Owner為null.

    因為還沒有線程去獲得Monitor鎖。

    簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖
  2. 多個線程競争,隻有線程1成功。其他進入阻塞隊列。(或者說,隻要owner非空,那麼其他線程就要進入阻塞隊列)

    當線程1執行完畢後,通知阻塞隊列裡的線程,引起它們的非公平性競争。

    簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖
  3. 若此時Owner線程調用wait方法,那麼會進入WaitSet。

    當被喚醒時(如調用notify())會進入EntryList重新競争。

輕量級鎖

如果一個對象雖然有多線程通路,但多線程通路的時間是錯開的(或者說沒有競争)的話,可以使用輕量級鎖來優化。

當有競争時,會發生鎖膨脹,變為重量級鎖,

java 對象頭

以32為虛拟機為例

普通對象是:

簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖
  • Klass Word:是一個指針,通過他可以知道是個啥類對象
  • Mark Word:
    簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖

    如 Normal ,即沒有上鎖,末尾兩位是01;輕量級鎖是00

    下面用Hashcode age Bias 01 代替最初的Mark Word

原理

簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖

每個線程的棧幀都會包含一個鎖記錄的結構,内部可以儲存鎖對象的Mark Word

  • 當線程執行到臨界區代碼時,對obj上鎖。讓鎖記錄中的Object reference 指向鎖對象,并嘗試用cas替換Object的Mark Word,将 Mark Word的值存入鎖記錄。
    • cas替換成功:那麼Object對象就會存儲鎖記錄狀态00和位址 ,表示由該線程給對象加鎖。 當Mark Word末尾是01時,才可以替換成功
      簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖
    • cas替換失敗
      • case 1:其他線程已經持有了改Object的輕量級鎖,表明有競争,進入鎖膨脹過程
      • case 2:自己執行synchronized鎖重入,再添加一條Lock Record作為重入的計數
        簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖

        此時進行cas操作自然會失敗。

        最後再講鎖膨脹。

    • 解鎖
      • 當Lock Record 記錄裡面存在null,說明存在重進入,這時重置鎖記錄,表示重進入計數減一
      • 鎖記錄不為空,這時用cas操作把鎖記錄裡面儲存的Mark Word替換給對象頭
        • 成功則解鎖成功
        • 失敗說明進行了鎖膨脹或已經更新為重量級鎖,進入重量級鎖的解鎖流程。

鎖膨脹

在嘗試加輕量級鎖的過程中,CAS操作無法成功,一個原因就是有其他線程為此對象加上了輕量級鎖,這時需要進行鎖膨脹,将輕量級鎖變為重量級鎖。

如線程1持有object的鎖,這時線程2也想要,但進行cas操作失敗,這時候會發生:

  • 為object對象申請Monitor鎖,讓object指向重量級鎖位址(此時後兩位為10,即重量級鎖)
  • 然後線程2進入Monitor的EntryList阻塞
    簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖

    當線程1執行完後,想要退出臨界區,使用cas将Mark Word的值替換給對象頭,但是會失敗。因為object的Mark Word後兩位變為10,已經不再是00了。

    這時按照Monitor位址找到monitor對象,設定Owner為null,再喚醒線程2.

偏向鎖

如果隻有一個線程,它多次重進入,那會多次建立空的鎖記錄,作為鎖重入的計數。

是以,java 6中引入了偏向鎖來做進一步優化:隻有第一次使用CAS将線程ID設定到對象的Mark Word,之後隻要發現這個線程ID是自己的就表示沒有發生競争,不用重新CAS。以後隻要不發生競争,這個對象就歸該線程所有。

當線程請求到鎖對象後,将鎖對象的狀态标志位改為01,即偏向模式。然後使用CAS操作将線程的ID記錄在鎖對象的Mark Word中。以後該線程可以直接進入同步塊,連CAS操作都不需要。但是,一旦有第二條線程需要競争鎖,那麼偏向模式立即結束,進入輕量級鎖的狀态。

簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖
簡單了解重量級鎖、輕量級鎖、偏向鎖重量級鎖輕量級鎖偏向鎖

參考:https://www.bilibili.com/video/BV16J411h7Rd?from=search&seid=659615821890387531