java Synchronized的優化

香港十大金曲發表於2019-03-29

Synchronized實現:

  1. 如果synchronized關鍵字修飾程式碼塊,編譯後的程式碼以monitorenter和monitorexit包圍起來
  2. 如果synchronized修飾的方法,方法的flag中會有ACC_SYNCHRONIZED標記.方法執行時,如果flag中有ACC_SYNCHRONIZED時,會去爭取monitor 每一個物件都有一個監視器鎖(monitor),當monitor被佔用時標識物件已經被鎖定了

java物件頭

java物件在記憶體中的儲存分為以下幾部分

  1. 物件頭

1-1 物件頭

  1. 例項資料
  2. 對其填充

重量級鎖(多個執行緒共同競爭同一個臨界區)

Synchronized是通過物件內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴於底層的作業系統的Mutex Lock來實現的。而作業系統實現執行緒之間的切換這就需要從使用者態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什麼Synchronized效率低的原因

臨界區: 需要獲取到物件監視器鎖(monitor)才能執行的部分程式碼。

輕量級鎖 (多個執行緒交替的進入臨界區)

本意為執行緒交替執行臨界區程式碼,並不存在競爭的情況,如果同一時間多個執行緒競爭訪問同一個鎖,輕量級鎖會膨脹成重量級鎖
由物件頭的情況來看,當輕量級鎖的情況下,物件頭記憶體放的指向站內鎖記錄的指標

什麼是鎖記錄:
1.輕量級鎖,會在當前的執行緒棧中,分配一塊名字叫Lock Record(鎖記錄)的空間,用於拷貝鎖物件的mark word
2.當輕量級鎖釋放的時候,鎖記錄中的mark word會重新寫會鎖物件中。

輕量級鎖的加鎖過程

  1. 在程式碼進入同步塊的時候,如果同步物件鎖狀態為無鎖狀態(鎖標誌位為“01”狀態,是否為偏向鎖為“0”),虛擬機器首先將在當前執行緒的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於儲存鎖物件目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。這時候執行緒堆疊與物件頭的狀態如圖2.1所示。
  2. 拷貝物件頭中的Mark Word複製到鎖記錄中。
  3. 拷貝成功後,虛擬機器將使用CAS操作嘗試將物件的Mark Word更新為指向Lock Record的指標,並將Lock record裡的owner指標指向object mark word。如果更新成功,則執行步驟4,否則執行步驟5。
  4. 如果這個更新動作成功了,那麼這個執行緒就擁有了該物件的鎖,並且物件Mark Word的鎖標誌位設定為“00”,即表示此物件處於輕量級鎖定狀態,這時候執行緒堆疊與物件頭的狀態如圖2.2所示。
  5. 如果這個更新操作失敗了,虛擬機器首先會檢查物件的Mark Word是否指向當前執行緒的棧幀,如果是就說明當前執行緒已經擁有了這個物件的鎖,那就可以直接進入同步塊繼續執行。否則說明多個執行緒競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標誌的狀態值變為“10”,Mark Word中儲存的就是指向重量級鎖(互斥量)的指標,後面等待鎖的執行緒也要進入阻塞狀態。 而當前執行緒便嘗試使用自旋來獲取鎖,自旋就是為了不讓執行緒阻塞,而採用迴圈去獲取鎖的過程。

加鎖成功後:
1.物件頭mark word 標記為00
2.鎖記錄是原先物件頭中mark word的拷貝,並且鎖記錄中的owner執行物件頭的mark word
3.物件頭中原先mark word的內容是指向鎖記錄的指標

輕量級鎖的解鎖

  1. 通過CAS操作嘗試把執行緒中複製的Displaced Mark Word物件替換當前的Mark Word。
  2. 如果替換成功,整個同步過程就完成了。
  3. 如果替換失敗,說明有其他執行緒嘗試過獲取該鎖(此時鎖已膨脹),那就要在釋放鎖的同時,喚醒被掛起的執行緒。

偏向鎖(一個執行緒使用一把鎖的情況)

如果只有一個執行緒競爭鎖的情況下,jvm只需要使用偏向鎖就可以了。因為偏向鎖更加的高效,由上面的介紹可以看到,加偏向鎖時,會涉及到mark word內容的拷貝,以及多個cas操作。偏向鎖更加的方便,只需要做一次cas 輕量級鎖是為了線上程交替執行同步塊時提高效能,而偏向鎖則是在只有一個執行緒執行同步塊時進一步提高效能。

偏向鎖的加鎖過程

  1. 訪問Mark Word中偏向鎖的標識是否設定成1,鎖標誌位是否為01——確認為可偏向狀態(無鎖和偏向鎖時都是01)。
  2. 如果為可偏向狀態,則測試執行緒ID是否指向當前執行緒,如果是,進入步驟(5),否則進入步驟(3)。
  3. 如果執行緒ID並未指向當前執行緒,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中執行緒ID設定為當前執行緒ID,然後執行(5);如果競爭失敗,執行(4)。
  4. 如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全域性安全點(safepoint)時獲得偏向鎖的執行緒被掛起,偏向鎖升級為輕量級鎖,然後被阻塞在安全點的執行緒繼續往下執行同步程式碼。
  5. 執行同步程式碼

偏向鎖的釋放

偏向鎖不會主動釋放,偏向鎖只有遇到其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖。偏向鎖的撤銷,需要等待全域性安全點(在這個時間點上沒有位元組碼正在執行),它會首先暫停擁有偏向鎖的執行緒,判斷鎖物件是否處於被鎖定狀態,撤銷偏向鎖後恢復到未鎖定(標誌位為“01”)或輕量級鎖(標誌位為“00”)的狀態

歸納(Synchronized的優化方式):

上面介紹了重量級鎖、輕量級鎖、偏向鎖,其實輕量級鎖和偏向鎖都是jvm對Synchronized的一個優化(當競爭還不是很緊張的時候,採用輕巧一點的辦法)

其他的優化方式

當然jvm還有別的優化,比如:

輕量級鎖膨脹成重量級鎖時候的自旋

因為獲取重量級鎖時候,執行緒上下文切換會是效率低下的原因之一。所以,在當前執行緒去競爭重量級鎖時,使用自旋的方式,不放棄cpu資源,如果能在自旋期間獲取到了鎖也是減少了執行緒上下文的切換。

由於獲取到鎖的時間不確定性,所以jvm對鎖自旋的時間採用了自適應的方式。即:如果上次在自旋期間獲取到鎖了,那麼這次自旋的時間的適當加長,如果上次沒有在自旋期間加到鎖,那麼這次自旋的時間適當減短。

鎖粗化

將多個連續在一次的加鎖解鎖操作合併成一個大的鎖操作

鎖消除

鎖消除即刪除不必要的加鎖操作。根據程式碼逃逸技術,如果判斷到一段程式碼中,堆上的資料不會逃逸出當前執行緒,那麼可以認為這段程式碼是執行緒安全的,不必要加鎖

參考資料:

www.cnblogs.com/paddix/p/54… www.zhihu.com/question/53…

相關文章