1. 程式人生 > >鎖原理:偏向鎖、輕量鎖、重量鎖

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

 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,所以重量級鎖適合用在同步塊執行時間長的情況下。

四、參考