1. 程式人生 > >【Java併發】synchronized之偏向鎖和輕量級鎖

【Java併發】synchronized之偏向鎖和輕量級鎖

synchronized之偏向鎖和輕量級鎖

上下文切換

即使是單核處理器也支援多執行緒執行程式碼執行程式碼,CPU通過給每個執行緒分配CPU時間來實現這個機制。CPU不停地切換執行緒執行,讓我們感覺到多個執行緒是同時執行的。

CPU通過時間片分配演算法來迴圈執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任務的狀態,以便下次切換回這個任務時,可以再載入這個任務的狀態。所以任務從儲存到再載入的過程就是一次上下文切換。

一個生動的例子
當我們在讀一本英語的技術書時,發現某個單詞不認識,於是便開啟英文字典,但是放下英文技術書之前,大腦必須先記住這本書讀到了多少頁的第多少行,等查完單詞之後,能夠繼續讀這本書。這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多執行緒的執行速度。

synchronized

synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖。Java 1.6中為了減少獲得鎖和釋放鎖帶來的效能消耗而引入了偏向鎖和輕量級鎖。

synchronized用的鎖存在Java物件頭中。

Java物件頭的儲存結構如下:

長度 內容 說明
32/64bit Mark Word 儲存物件的hashCode或鎖訊息等
32/64bit Class Metadata Address 儲存到物件型別資料的指標
32/64bit Array length 陣列的長度(如果當前物件是陣列)

Mark Word的儲存結構如下:

鎖狀態 內容 鎖標誌位
無鎖狀態 hashCode | 物件分代年齡 | 偏向鎖標誌位0 01
偏向鎖 執行緒ID | Epoch | 物件分代年齡 | 偏向鎖標誌位1 01
輕量級鎖 指向棧中鎖記錄的指標 00
重量級鎖 指向互斥量(重量級鎖)的指標 01

鎖的升級與對比

鎖一共有四種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級。

偏向鎖

偏向鎖加鎖:

  1. 測試執行緒ID是否指向當前執行緒,如果是,進入步驟4,否則進入步驟2。
  2. 如果執行緒ID並未指向當前執行緒,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中執行緒ID設定為當前執行緒ID,然後執行4;如果競爭失敗,執行3。
  3. 如果CAS獲取偏向鎖失敗,則表示有競爭,撤銷偏向鎖。
  4. 執行同步程式碼。

偏向鎖撤銷:

只有當其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖。偏向鎖的撤銷,需要等待全域性安全點。

它會首先暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否活著,如果不處於活動狀態,則將物件頭設定為無鎖狀態。

如果執行緒仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向物件的鎖記錄,棧中的鎖記錄和物件頭的Mark Word要麼偏向於其他執行緒,要麼恢復到無鎖或者輕量級鎖狀態。

輕量級鎖

輕量級鎖加鎖:

  1. 虛擬機器首先將在當前執行緒的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於儲存鎖物件目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。
  2. 拷貝物件頭中的Mark Word複製到鎖記錄中。
  3. 拷貝成功後,虛擬機器將使用CAS操作嘗試將物件的Mark Word更新為指向Lock Record的指標,如果更新成功,則執行步驟4,否則執行步驟5
  4. 如果這個更新動作成功了,那麼這個執行緒就擁有了該物件的鎖,並且物件Mark Word的鎖標誌位設定為“00”。
  5. 如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。

輕量級鎖解鎖:
會使用CAS將Displaced Mark Word替換回物件頭,如果成功,表示沒有競爭,如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

參考

  1. Java併發程式設計的藝術[書籍]
  2. java 中的鎖 – 偏向鎖、輕量級鎖、自旋鎖、重量級鎖