JAVA鎖的膨脹過程和優化

轉載自:https://www.cnblogs.com/dsj2016/p/5714921.html
感謝原博主!此文說的很詳細!


首先說一下鎖的優化策略。

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成一個指向鎖記錄的指針。成功了就拿到了鎖。那麼失敗了呢?失敗了的說法比較多。主流有《深入理解JVM》的說法和《併發編程的藝術》的說法。

《深入理解JVM》的說法:

失敗了,去查看MarkWord的值。有2種可能:1,指向當前線程的指針,2,別的值。

如果是1,那麼說明發生了“重入”的情況,直接當做成功獲得鎖處理。

其實這個有個疑問,爲什麼獲得鎖成功了而CAS失敗了,這裏其實要牽扯到CAS的具體過程:先比較某個值是不是預測的值,是的話就動用原子操作交換(或賦值),否則不操作直接返回失敗。在用CAS的時候期待的值是其原本的MarkWord。發生“重入”的時候會發現其值不是期待的原本的MarkWord,而是一個指針,所以當然就返回失敗,但是如果這個指針指向這個線程,那麼說明其實已經獲得了鎖,不過是再進入一次。如果不是這個線程,那麼情況2:

如果是2,那麼發生了競爭,鎖會膨脹爲一個重量級鎖(MutexLock)

《併發編程的藝術》的說法:

失敗了直接自旋。期望在自旋的時間內獲得鎖,如果還是不能獲得,那麼開始膨脹,修改鎖的MarkWord改爲重量級鎖的指針,並且阻塞自己。

解鎖過程:(那個拿到鎖的線程)用CAS把MarkWord換回到原來的對象頭,如果成功,那麼沒有競爭發生,解鎖完成。如果失敗,表示存在競爭(之前有線程試圖通過CAS修改MarkWord),這時要釋放鎖並且喚醒阻塞的線程。

發佈了72 篇原創文章 · 獲贊 45 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章