java Synchronized的優化

Synchronized實現:

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

java對象頭

java對象在內存中的存儲分爲如下幾部分html

  1. 對象頭

1-1 對象頭

  1. 實例數據
  2. 對其填充

重量級鎖(多個線程共同競爭同一個臨界區)

Synchronized是經過對象內部的一個叫作監視器鎖(monitor)來實現的。可是監視器鎖本質又是依賴於底層的操做系統的Mutex Lock來實現的。而操做系統實現線程之間的切換這就須要從用戶態轉換到核心態,這個成本很是高,狀態之間的轉換須要相對比較長的時間,這就是爲何Synchronized效率低的緣由java

臨界區: 須要獲取到對象監視器鎖(monitor)才能執行的部分代碼。安全

輕量級鎖 (多個線程交替的進入臨界區)

本意爲線程交替執行臨界區代碼,並不存在競爭的狀況,若是同一時間多個線程競爭訪問同一個鎖,輕量級鎖會膨脹成重量級鎖
由對象頭的狀況來看,當輕量級鎖的狀況下,對象頭內存放的指向站內鎖記錄的指針jvm

什麼是鎖記錄:
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…

相關文章
相關標籤/搜索