JAVA之鎖機制實現原理(簡化版)

併發編程離不開鎖,然而每次遇到鎖問題時都會談鎖色變,下面對鎖實現做簡單化描述,方便你們容易理解!java

1、緒論

在JAVA中鎖一共有四種狀態:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態(按從低到高順序,鎖着競爭狀況逐漸升級)算法

JAVA中鎖只能升級卻不能降級,目的是爲了提升得到鎖和釋放鎖的效率。 編程

2、對象頭的介紹

在HopSpot虛擬機中,對象在內存存儲中分爲3部分:對象頭(Header)、實例數據(Instance Data) 和 對齊填充(Padding)。 想了解Java對象結構的詳細信息請看:java對象結構安全

java對象頭包含3部分信息,以下: 多線程

鎖的狀態保存在對象頭的Mark Word中,以32位JDK爲例: 併發

3、鎖狀態的介紹

一、偏向鎖

在無多線程競爭鎖的狀況下,爲了讓同一線程得到鎖的代價更低而引入了偏向鎖。學習

1.一、偏向鎖的獲取過程
  • (1) 訪問Mark Word中偏向鎖的標識是否設置成1,即鎖的標誌爲是否爲01——確認爲可偏向狀態。
  • (2) 若是爲可偏向狀態,則測試線程ID是否指向當前線程,若是是,進入步驟(5),不然進入步驟(3)。
  • (3) 若是線程ID並未指向當前線程,則經過CAS操做競爭鎖。若是競爭成功,則將Mark Word中線程ID設置爲當前線程ID,而後執行(5);若是競爭失敗,執行(4)。
  • (4) 若是CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時得到偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,而後被阻塞在安全點的線程繼續往下執行同步代碼。
  • (5) 執行同步代碼。

CAS是一種無鎖算法,CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。 測試

1.二、偏向鎖的釋放

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,因此當其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。 偏向鎖的撤銷,須要等待全局安全點(在這個時間點上沒有正在執行的字節碼),它會先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態,撤銷偏向鎖後恢復到未鎖定(標誌位爲「01」)或輕量級鎖(標誌位爲「00」)的狀態。優化

二、輕量級鎖

2.一、輕量級鎖加鎖

線程在執行同步塊以前,JVM會先在當前線程的棧幀中建立用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄(Lock record)中,官方成爲Displaced Mark Word。而後嘗試使用CAS算法將對象頭中的Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。操作系統

2.二、輕量級鎖解鎖

輕量級鎖解鎖時,會使用原子的CAS操做將Displaced Mark Word替換回對象頭,若是成功,則表示沒有競爭發生。若是失敗,表示當前存在競爭,鎖就會膨脹成重量級鎖。

由於自旋會消耗CPU,爲了不無用的自旋(好比得到鎖的線程被阻塞了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。其餘線程試圖獲取鎖時,會被阻塞住,當持有鎖的線程釋放以後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖競爭。

三、重量級鎖

在多線程併發編程中synchronized一直是元老級角色,不少人稱呼它爲「重量級鎖」。synchronized是經過對象內部的一個叫作監視器鎖(monitor)來實現的,可是監視器鎖本質又是依賴於底層操做系統的Mutex Lock來實現的,而操做系統實現線程之間的切換就須要從用戶狀態切換到核心狀態,這個成本很高,狀態之間轉換須要相對比較長的時間,這就是synchronized效率低的緣由。所以,這種依賴於操做系統Mutex Lock來實現的鎖,咱們稱之爲「重量級鎖」。

在JDK1.6後,synchronized獲得了種種優化後,在某些狀況下已經沒那麼重了。

4、鎖的優缺點對比

注:本文主要觀點來源於《java併發編程的藝術》和我的學習網上的文章的一些總結。

相關文章
相關標籤/搜索