1. 程式人生 > >Synchronized與三種鎖態

Synchronized與三種鎖態

1 物件頭與鎖

要了解Synchronized的鎖,必須知道物件頭是怎麼回事。因此這個鎖就儲存在物件頭中。Hotpot虛擬機器的物件頭分兩部分資訊,第一部分用於儲存物件自身的執行時資料,如HashCode,GC分代年齡等,這部分資料長度在32位和64位虛擬機器中分別為32bit和64bit,它又稱為“MarkWord”,它是實現鎖的關鍵。另一部分就是用於儲存指向方法區物件型別資料的指標,如果是陣列的話,還有一個額外的空間儲存陣列長度。

物件頭是與物件自己資料無關的額外儲存成本,因此考慮到空間效率,MarkWord會根據自身的狀態進行復用,也就是說在不同的狀態下,它的儲存結構不一樣。它的變化狀態如下所示

在32位的HotSpot虛擬機器中物件未鎖定的狀態下,Mark Word的32bit空間中的25bit用於儲存物件的雜湊碼,4bit用於儲存物件分代年齡,2bit用於儲存鎖標誌位,1bit固定為0。

2 自旋鎖與自適應自旋

通常我們稱Sychronized鎖是一種重量級鎖,是因為在互斥狀態下,沒有得到鎖的執行緒會被掛起阻塞,而掛起執行緒和恢復執行緒的操作都需要轉入核心態中完成。同時,虛擬機器開發團隊也注意到,許多應用上的資料鎖只會持續很多的一段時間,如果為了這段時間去掛起和恢復執行緒是不值得的,所以引入了自旋鎖。所謂的自旋,就是讓沒有獲得鎖的執行緒自己執行一段時間的自迴圈,這就是自旋鎖。自旋鎖在JDK6以後已經預設開啟,可以通過-XX:+UseSpinning引數來開啟。

但這顯然並不是最好的一種方法,不掛起執行緒的代價就是該執行緒會一直佔用處理器。如果鎖被佔用的時間很短,自旋等待的效果就會很好,反之,自旋會消耗大量處理器資源。因此,自旋的等待時間必須有一定的限度,如果超過限度還沒有獲得鎖,就要掛起執行緒,這個限度預設是10次,可以使用-XX:PreBlockSpin改變。

在JDK6以後又引入了自適應自旋鎖,也就說自旋的時間限度不是一個固定值了,而是由上一次同一個鎖的自旋時間及鎖的擁有者狀態來決定。虛擬機器認為,如果同一個鎖物件自旋剛剛成功獲得鎖,那麼下一次很可能獲得鎖,所以允許這次自旋鎖自旋很長時間、而如果某個鎖很少獲得鎖,那麼以後在獲取鎖的過程中可能忽略到自旋過程。

 

3 鎖的升級過程

在Java 6中為了減少獲得鎖和釋放鎖帶來的效能消耗,引入了“偏向鎖”和“輕量級鎖”,在Java中,鎖共有4種狀態,級別從低到高依次為:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級。

 

4 偏向鎖

偏向鎖實際上是一種鎖優化的,其目的是為了減少資料在無競爭情況下的效能消耗。其核心思想就是鎖會偏向第一個獲取它的執行緒,在接下來的執行過程中該鎖沒有其他的執行緒獲取,則持有偏向鎖的執行緒永遠不需要再進行同步。

 

4.1 偏向鎖的獲取

當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID。以後該執行緒在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需要檢查當前Mark Word中儲存的執行緒是否指向當前執行緒,如果成功,表示已經獲得物件鎖;如果檢測失敗,則需要再測試一下Mark Word中偏向鎖的標誌是否已經被置為1(表示當前鎖是偏向鎖):如果沒有則使用CAS操作競爭鎖,如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。


4.2 偏向鎖的撤銷

偏向鎖使用一種等待競爭出現才釋放鎖的機制,所以當有其他執行緒嘗試獲得鎖時,才會釋放鎖。偏向鎖的撤銷,需要等到安全點。它首先會暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否活著,如果不處於活動狀態,則將物件頭設定為無鎖狀態;如果依然活動,擁有偏向鎖的棧會被執行,遍歷偏向物件的鎖記錄,棧中的鎖記錄和物件頭的Mark Word要麼重新偏向其他執行緒,要麼恢復到無鎖或者標記物件不合適作為偏向鎖(膨脹為輕量級鎖),最後喚醒暫停的執行緒。

4.3 關閉偏向鎖

偏向鎖在Java執行環境中預設開啟,但是不會隨著程式啟動立即生效,而是在啟動幾秒種後才啟用,可以使用引數關閉延遲:

-XX:BiasedLockingStartupDelay=0 

同樣可以關閉偏向鎖

 -XX:UseBiasedLocking=false,那麼程式預設進入輕量級鎖。

5 輕量級鎖

輕量級鎖是JDK1.6之中加入的新型鎖機制,它並不是來代替重量級鎖的,他的本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗。

5.1 輕量級鎖加鎖

執行緒在執行同步塊之前,JVM會現在當前執行緒的棧幀中建立用於儲存鎖記錄的空間(LockRecord),並將物件頭的Mark Word資訊複製到鎖記錄中。然後執行緒嘗試使用CAS將物件頭的MarkWord替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,並且物件的鎖標誌位轉變為“00”,如果失敗,表示其他執行緒競爭鎖,當前執行緒便會嘗試自旋獲取鎖。如果有兩條以上的執行緒競爭同一個鎖,那麼輕量級鎖就不再有效,要膨脹為重量級鎖,鎖標誌的狀態變為“10”,MarkWord中儲存的就是指向重量級鎖(互斥量)的指標,後面等待的執行緒也要進入阻塞狀態。

5.2 輕量級鎖解鎖

輕量級鎖解鎖時,同樣通過CAS操作將物件頭換回來。如果成功,則表示沒有競爭發生。如果失敗,說明有其他執行緒嘗試過獲取該鎖,鎖同樣會膨脹為重量級鎖。在釋放鎖的同時,喚醒被掛起的執行緒。