Java:synchronized關鍵字引出的多種鎖

EricTao2發表於2019-07-05

Java:synchronized關鍵字引出的多種鎖

前言

Java 中的 synchronized關鍵字可以在多執行緒環境下用來作為執行緒安全的同步鎖。本文不討論 synchronized 的具體使用,而是研究下synchronized底層的鎖機制,以及這些鎖分別的優缺點。

一 synchronized機制

synchronized關鍵字是JAVA中常用的同步功能,提供了簡單易用的鎖功能。
synchronized有三種用法,分別為:

  • 用在普通方法上,能夠鎖住當前物件。
  • 用在靜態方法上,能夠鎖住類
  • 用在程式碼塊上,鎖住的是synchronized()裡的物件

在JDK6之前,synchronized使用的是重量級鎖制,在之後synchronized加入了鎖膨脹機制,顯著提升了synchronized關鍵字的效率。

基於synchronized關鍵字,我們來了解下幾種類別的鎖,並且講解synchronized的鎖膨脹機制。

synchronized鎖是非公平鎖。並且一個被synchronized鎖住的物件或類,就是一把鎖。

另外一提,所有鎖都是儲存在Java物件頭裡的,Java物件頭裡的Mark Word裡預設儲存物件的HashCode,分代年齡和鎖標記位。也就是說Mark Word記錄了鎖的狀態

二 鎖膨脹機制與幾類鎖

鎖膨脹是不可逆的

2.1 偏向鎖

synchronized在JDK1.6以後預設開啟偏向鎖synchronized最初都是偏向鎖

表現:一個執行緒獲取鎖成功後,會在物件頭裡記錄執行緒ID,以後該執行緒獲取和釋放鎖都沒有任何花費。(因為該鎖已經被繫結在該執行緒上了,且在膨脹前不會改變),如果其他執行緒嘗試獲取這個鎖,偏向鎖將會膨脹為輕量鎖

優點:在只有一個執行緒使用鎖的時候獲取和退出鎖沒有任何花費

缺點:鎖競爭激烈會很快升級為輕量鎖,那麼維持偏向鎖的過程就是在浪費計算機資源。(不過因為偏向鎖本身就很輕量,因此浪費的資源並不多)

小結:只有一個執行緒使用鎖的情況下,synchronized使用的鎖為偏向鎖
如果鎖競爭激烈,可以通過配置JDK禁用偏向鎖

2.2 輕量鎖

一把鎖不止一個執行緒使用,則偏向鎖膨脹為輕量鎖

表現:執行緒獲取輕量鎖時,會直接用CAS修改物件頭裡鎖的記錄,如果修改失敗,代表此時鎖存在多個執行緒的競爭,輕量鎖將會膨脹為重量鎖

優點:線上程之間使用鎖不存在競爭時,一次CAS操作就能獲取和退出鎖

缺點:與偏向鎖類似

小結:只要一把鎖不止一個執行緒獲取過,偏向鎖就會膨脹為輕量鎖

2.3 重量鎖

一把鎖存在多執行緒競爭,則輕量鎖開始自旋,自旋一定次數後仍沒獲取鎖,則膨脹為重量鎖(存在競爭時,輕量鎖雖然會先自旋,但是最終往往都會膨脹為重量鎖)

表現:執行緒獲取重量鎖時,如果獲取失敗(即鎖已被其他執行緒獲取),則使用自適應自旋鎖,自旋一定次數後仍沒獲取鎖,則進入阻塞佇列等待。

優點:未獲取到的鎖進入阻塞佇列,節約CPU資源。(好吧感覺其實是沒有啥優點)

缺點:重量鎖是通過物件內部的監視器(monitor)實現,其中monitor的本質是依賴於底層作業系統的Mutex Lock實現,作業系統實現執行緒之間的切換需要從使用者態到核心態的切換,切換成本非常高。

小結:只要一把鎖存在多執行緒競爭,輕量鎖就會膨脹為重量鎖

自旋鎖

synchronized輕量鎖重量鎖,使用了自適應自旋鎖進行效能優化

首先介紹自旋鎖

表現:執行緒獲取鎖失敗後,不會進入阻塞等待,而是再次嘗試去獲取鎖,如此反覆,直到獲取到鎖,或者自旋結束那麼會阻塞等待。

解決問題:在某些場景下,執行緒持有鎖的時間非常短。線上程獲取鎖失敗後,如果執行緒進入阻塞將會帶來執行緒上下文的切換,上下文切換的時間可能反而高於執行緒反覆嘗試獲取鎖的時間。
此時執行緒原地等待去重複獲取鎖。反而在效能上更有優勢。

缺點:

  1. 單核CPU沒有執行緒並行,反覆嘗試會導致程式無法繼續執行。
  2. 重複嘗試導致了CPU的佔用,如果CPU資源緊張的話反而會效能下降
  3. 如果鎖的競爭時間過長,不僅沒有效能提升,還浪費了大量CPU資源。

優化:使用自適應自旋鎖。自適應自旋鎖會根據之前的鎖獲取記錄,優化調整自旋時間,避免造成不必要的自旋。

三 具體synchronized流程

Java:synchronized關鍵字引出的多種鎖

相關文章