鎖原理:偏向鎖、輕量鎖、重量鎖


 java中每個對象都可作為鎖,鎖有四種級別,按照量級從輕到重分為:無鎖、偏向鎖、輕量級鎖、重量級鎖。每個對象一開始都是無鎖的,隨着線程間爭奪鎖,越激烈,鎖的級別越高,並且鎖只能升級不能降級。

一、java對象頭

 鎖的實現機制與java對象頭息息相關,鎖的所有信息,都記錄在java的對象頭中。用2字(32位JVM中1字=32bit=4baye)存儲對象頭,如果是數組類型使用3字存儲(還需存儲數組長度)。對象頭中記錄了hash值、GC年齡、鎖的狀態、線程擁有者、類元數據的指針。

Java對象頭的結構
在不同鎖狀態下Mark Word的結構(32位下)

二、偏向鎖

 在實際應用運行過程中發現,“鎖總是同一個線程持有,很少發生競爭”,也就是說鎖總是被第一個占用他的線程擁有,這個線程就是鎖的偏向線程。

 那么只需要在鎖第一次被擁有的時候,記錄下偏向線程ID。這樣偏向線程就一直持有着鎖,直到競爭發生才釋放鎖。以后每次同步,檢查鎖的偏向線程ID與當前線程ID是否一致,如果一致直接進入同步,退出同步也,無需每次加鎖解鎖都去CAS更新對象頭,如果不一致意味着發生了競爭,鎖已經不是總是偏向於同一個線程了,這時候需要鎖膨脹為輕量級鎖,才能保證線程間公平競爭鎖。

1.加鎖


偏向鎖加鎖發生在偏向線程第一次進入同步塊時,CAS原子操作嘗試更新對象的Mark Word(偏向鎖標志位為"1",記錄偏向線程的ID)。

2.撤銷偏向鎖

 當有另一個線程來競爭鎖的時候,就不能再使用偏向鎖了,要膨脹為輕量級鎖。
競爭線程嘗試CAS更新對象頭失敗,會等待到全局安全點(此時不會執行任何代碼)撤銷偏向鎖。
撤銷偏向鎖的過程
撤銷偏向鎖的過程

三、輕量級鎖

 輕量鎖與偏向鎖不同的是:

  1. 輕量級鎖每次退出同步塊都需要釋放鎖,而偏向鎖是在競爭發生時才釋放鎖
  2. 每次進入退出同步塊都需要CAS更新對象頭
  3. 爭奪輕量級鎖失敗時,自旋嘗試搶占鎖

 可以看到輕量鎖適合在競爭情況下使用,其自旋鎖可以保證響應速度快,但自旋操作會占用CPU,所以一些計算時間長的操作不適合使用輕量級鎖。

1.加鎖

 加鎖過程和偏向鎖加鎖差不多,也是CAS修改對象頭,只是修改的內容不同。

  1. 在MarkWord中保存當前線程的指針
  2. 修改鎖標識位為“00”

采用CAS操作的原因是,不想在加鎖解鎖上再加同步

 如果對象處於無鎖狀態(偏向鎖標志位為"0",鎖標志位為"01"),會在線程的棧中開辟個鎖記錄空間(Lock Record),將Mark Word拷貝一份到Lock Record中,稱為Displaced Mark Word,在Lock Record中保存對象頭的指針(owner)。
接下來CAS更新MarkWord,將MarkWord指向當前線程,owner指向MarkWord,如果失敗了,則意味着出現了另一個線程競爭鎖,此時需要鎖膨脹為輕量級鎖。

CAS操作前的棧和MarkWord狀態
CAS操作后的棧和MarkWord狀態

2.解鎖

 用CAS操作鎖置為無鎖狀態(偏向鎖位為"0",鎖標識位為"01"),若CAS操作失敗則是出現了競爭,鎖已膨脹為重量級鎖了,此時需要釋放鎖(持有重量級鎖線程的指針位為"0",鎖標識位為"10")並喚醒重量鎖的線程。

3.膨脹為重量級鎖

 當競爭線程嘗試占用輕量級鎖失敗多次之后,輕量級鎖就會膨脹為重量級鎖,重量級線程指針指向競爭線程,競爭線程也會阻塞,等待輕量級線程釋放鎖后喚醒他。
膨脹為重量級鎖

三、重量級鎖

 重量級鎖的加鎖、解鎖過程和輕量級鎖差不多,區別是:競爭失敗后,線程阻塞,釋放鎖后,喚醒阻塞的線程,不使用自旋鎖,不會那么消耗CPU,所以重量級鎖適合用在同步塊執行時間長的情況下。

四、參考

  1. 《Java並發編程的藝術》
  2. 《輕量級鎖與偏向鎖》
  3. 《Synchronized下的三種鎖:偏向鎖 輕量鎖 重量鎖 理解》
  4. 《JAVA鎖的膨脹過程和優化》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM