鎖原理:偏向鎖、輕量鎖、重量鎖
java中每個物件都可作為鎖,鎖有四種級別,按照量級從輕到重分為:無鎖、偏向鎖、輕量級鎖、重量級鎖。每個物件一開始都是無鎖的,隨著執行緒間爭奪鎖,越激烈,鎖的級別越高,並且鎖只能升級不能降級。
一、java物件頭
鎖的實現機制與java物件頭息息相關,鎖的所有資訊,都記錄在java的物件頭中。用2字(32位JVM中1字=32bit=4baye)儲存物件頭,如果是陣列型別使用3字儲存(還需儲存陣列長度)。物件頭中記錄了hash值、GC年齡、鎖的狀態、執行緒擁有者、類元資料的指標。
二、偏向鎖
在實際應用執行過程中發現,“鎖總是同一個執行緒持有,很少發生競爭”,也就是說鎖總是被第一個佔用他的執行緒擁有,這個執行緒就是鎖的偏向執行緒。
那麼只需要在鎖第一次被擁有的時候,記錄下偏向執行緒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,如果失敗了,則意味著出現了另一個執行緒競爭鎖,此時需要鎖膨脹為輕量級鎖。
2.解鎖
用CAS操作鎖置為無鎖狀態(偏向鎖位為”0”,鎖標識位為”01”),若CAS操作失敗則是出現了競爭,鎖已膨脹為重量級鎖了,此時需要釋放鎖(持有重量級鎖執行緒的指標位為”0”,鎖標識位為”10”)並喚醒重量鎖的執行緒。
3.膨脹為重量級鎖
當競爭執行緒嘗試佔用輕量級鎖失敗多次之後,輕量級鎖就會膨脹為重量級鎖,重量級執行緒指標指向競爭執行緒,競爭執行緒也會阻塞,等待輕量級執行緒釋放鎖後喚醒他。
三、重量級鎖
重量級鎖的加鎖、解鎖過程和輕量級鎖差不多,區別是:競爭失敗後,執行緒阻塞,釋放鎖後,喚醒阻塞的執行緒,不使用自旋鎖,不會那麼消耗CPU,所以重量級鎖適合用在同步塊執行時間長的情況下。