阿里的人問什么是鎖膨脹,答不上來,回來做了總結:
關於鎖的膨脹,synchronized的原理參考:深入分析Synchronized原理(阿里面試題)
首先說一下鎖的優化策略。
1,自旋鎖
自旋鎖其實就是在拿鎖時發現已經有線程拿了鎖,自己如果去拿會阻塞自己,這個時候會選擇進行一次忙循環嘗試。也就是不停循環看是否能等到上個線程自己釋放鎖。這個問題是基於一個現實考量的:很多拿了鎖的線程會很快釋放鎖。因為一般敏感的操作不會很多。當然這個是一個不能完全確定的情況,只能說總體上是一種優化。
舉個例子就好比一個人要上廁所發現廁所里面有人,他可以:1,等一小會。2,跑去另外的地方上廁所。等一小會不一定能等到前一個人出來,不過如果跑去別的廁所的花費的時間肯定比等一小會結果前一個人出來了長。當然等完了結果那個人沒出來還是要跑去別的地方上廁所這是最慢的。
然后是基於這種做法的一個優化:自適應自旋鎖。也就是說,第一次設置最多自旋10次,結果在自旋的過程中成功獲得了鎖,那么下一次就可以設置成最多自旋20次。道理是:一個鎖如果能夠在自旋的過程中被釋放說明很有可能下一次也會發生這種事。那么就更要給這個鎖某種“便利”方便其不阻塞得鎖(畢竟快了很多)。同樣如果多次嘗試的結果是完全不能自旋等到其釋放鎖,那么就說明很有可能這個臨界區里面的操作比較耗時間。就減小自旋的次數,因為其可能性太小了。
2,鎖粗化
試想有一個循環,循環里面是一些敏感操作,有的人就在循環里面寫上了synchronized關鍵字。這樣確實沒錯不過效率也許會很低,因為其頻繁地拿鎖釋放鎖。要知道鎖的取得(假如只考慮重量級MutexLock)是需要操作系統調用的,從用戶態進入內核態,開銷很大(阿里面試)。於是針對這種情況也許虛擬機發現了之后會適當擴大加鎖的范圍(所以叫鎖粗化)以避免頻繁的拿鎖釋放鎖的過程。
3,鎖消除
通過逃逸分析發現其實根本就沒有別的線程產生競爭的可能(別的線程沒有臨界量的引用),而“自作多情”地給自己加上了鎖。有可能虛擬機會直接去掉這個鎖。
4,偏向鎖和輕量級鎖
這兩個鎖既是一種優化策略,也是一種膨脹過程所以一起說。首先它們的關系是:最高效的是偏向鎖,盡量使用偏向鎖,如果不能(發生了競爭)就膨脹為輕量級鎖,這樣優化的效率不如原來高不過還是一種優化(對比重量級鎖而言)。所以整個過程是盡可能地優化。
首先說說偏向鎖。
HotSpot的研究人員發現大多數情況下雖然加了鎖,但是沒有競爭的發生,甚至是同一個線程反復獲得這個鎖。那么偏向鎖就為了針對這種情況。
舉個例子,一個倉庫管理員管着鑰匙,然而每一次都是老王去借,倉庫管理員於是就認識了老王,直接和他說,“行,你直接拿就是不用填表格了我記得你”。
講一下偏向鎖的具體過程。首先JVM要設置為可用偏向鎖。然后當一個進程訪問同步塊並且獲得鎖的時候,會在對象頭和棧幀的鎖記錄里面存儲取得偏向鎖的線程ID。
下一次有線程嘗試獲取鎖的時候,首先檢查這個對象頭的MarkWord是不是儲存着這個線程的ID。如果是,那么直接進去而不需要任何別的操作。如果不是,那么分為兩種情況。1,對象的偏向鎖標志位為0(當前不是偏向鎖),說明發生了競爭,已經膨脹為輕量級鎖,這時使用CAS操作嘗試獲得鎖(這個操作具體是輕量級鎖的獲得鎖的過程下面講)。2,偏向鎖標志位為1,說明還是偏向鎖不過請求的線程不是原來那個了。這時只需要使用CAS嘗試把對象頭偏向鎖從原來那個線程指向目前求鎖的線程。這種情況舉個例子就是老王准備退休了,他兒子接替他來拿鑰匙,於是倉庫管理員認識了他兒子,他兒子每次來也不用登記注冊了。
這個CAS失敗了呢?首先必須明確這個CAS為什么會失敗,也就是說發生了競爭,有別的線程和它搶鎖並且搶贏了,那么這個情況下,它就會要求撤銷偏向鎖(因為發生了競爭)。接着它首先暫停擁有偏向鎖的線程,檢查這個線程是否是個活動線程,如果不是,那么好,你拿了鎖但是沒在干事,鎖還記錄着你,那么直接把對象頭設置為無鎖狀態重新來過。如果還是活動線程,先遍歷棧幀里面的鎖記錄,讓這個偏向鎖變為無鎖狀態,然后恢復線程。
再說輕量級鎖。這是偏向鎖膨脹之后的產物。
加鎖的過程:JVM在當前線程的棧幀中創建用於儲存鎖記錄的空間(LockRecord),然后把MarkWord放進去,同時生成一個叫Owner的指針指向那個加鎖的對象,同時用CAS嘗試把對象頭的MarkWord成一個指向鎖記錄的指針。至於synchronized的原理 也是阿里問的熱點問題
成功了就拿到了鎖。那么失敗了呢?失敗了的說法比較多。主流有《深入理解JVM》的說法和《並發編程的藝術》的說法。
《深入理解JVM》的說法:
失敗了,去查看MarkWord的值。有2種可能:1,指向當前線程的指針,2,別的值。
如果是1,那么說明發生了“重入”的情況,直接當做成功獲得鎖處理。
其實這個有個疑問,為什么獲得鎖成功了而CAS失敗了,這里其實要牽扯到CAS的具體過程:先比較某個值是不是預測的值,是的話就動用原子操作交換(或賦值),否則不操作直接返回失敗。在用CAS的時候期待的值是其原本的MarkWord。發生“重入”的時候會發現其值不是期待的原本的MarkWord,而是一個指針,所以當然就返回失敗,但是如果這個指針指向這個線程,那么說明其實已經獲得了鎖,不過是再進入一次。如果不是這個線程,那么情況2:
如果是2,那么發生了競爭,鎖會膨脹為一個重量級鎖(MutexLock)
《並發編程的藝術》的說法:
失敗了直接自旋。期望在自旋的時間內獲得鎖,如果還是不能獲得,那么開始膨脹,修改鎖的MarkWord改為重量級鎖的指針,並且阻塞自己。
解鎖過程:(那個拿到鎖的線程)用CAS把MarkWord換回到原來的對象頭,如果成功,那么沒有競爭發生,解鎖完成。如果失敗,表示存在競爭(之前有線程試圖通過CAS修改MarkWord),這時要釋放鎖並且喚醒阻塞的線程。