從細節理解鎖的升級

煢祇發表於2021-09-05

執行緒同步

  1. 保證互斥訪問,即一個物件被一個執行緒修改的時候,另一個執行緒不允許同時進行修改
  2. 保證進入同步方法或者同步程式碼塊的每個執行緒,都能看到之前的修改效果

鎖的升級

image-20210905095531290

以生活做類比,與鎖相關的還有一些概念,比如說鑰匙,櫃子.鑰匙用於開鎖,鎖用於保護櫃子裡的物資.只有得到了這把鑰匙,我們才能去訪問櫃子裡的資源.而鎖和鑰匙是一起交付的,不會出現只賣鎖不賣鑰匙的情形,因此正常情況下得到鎖就意味著我們一定能夠開啟這把鎖

java中的鎖也是一樣,只不過鎖的是同步程式碼塊,而獲得鎖的是執行緒,當一個執行緒獲取到一個鎖時也等價於獲取到了該鎖的鑰匙.也就能訪問到被鎖住的程式碼與資源.而其他執行緒在此時則無法訪問該塊程式碼.

無鎖

無鎖就意味著,任意執行緒任意時刻都可以訪問

偏向鎖

當第一個執行緒第一次訪問一個鎖時,會通過CAS將自己的ThreadID置換到MarkWord中,並將偏向鎖標誌位置為1,當該執行緒再次訪問此同步程式碼塊時首先判斷是否為偏向鎖,是則繼續判斷ThreadId是否相同,相同就直接進入同步程式碼塊,避免了再次用CAS的開銷,如果有第二個執行緒來訪問同一個程式碼塊,也會先判斷是否為偏向鎖以及ThreadID是否相等,不相等則利用CAS去替換自己的ThreadID,如果成功,則說明第一個執行緒已經不在了,此時依舊為偏向鎖.如果失敗,說明執行緒一依舊佔有鎖,此時則將執行緒一暫停,設定偏向鎖標識為0,並設定鎖標誌位為00,升級為輕量級鎖,會按照輕量級鎖的方式進行競爭鎖。

注意

  • 當一個物件已經計算過identity hash code,它就無法進入偏向鎖狀態;
  • 當一個物件當前正處於偏向鎖狀態,並且需要計算其identity hash code的話,則它的偏向鎖會被撤銷,並且鎖會膨脹為輕量級鎖或者重量鎖;

也就是說HashCode與偏向鎖不能共存,jdk15以後似乎有取消偏向鎖的趨勢

輕量級鎖

從無鎖->輕量級鎖

首先通過標誌位判斷此時同步塊是否被鎖定,如果沒有被鎖定,則在當前執行緒的棧幀中建立一個叫做Lock Record的空間,用於儲存物件(鎖)當前的Mark Word拷貝,同時還存有一個指標,然後再通過CAS嘗試把物件的MarkWord更新為指向LockRecord的指標(也就是說,如果此時鎖物件的MarkWordLockRecord裡的相同,那麼當前執行緒就可以獲得鎖)將LockRecord裡的另一個指標指向當前的MarkRecord(這個用於輕量級鎖的撤銷),更新成功後,將鎖標誌位轉化為00.

如果更新失敗了,就說明,有另一條執行緒已經獲取到了鎖,此時失敗的執行緒則通過自旋來不斷獲取鎖,如果在自旋過程中成功獲取了鎖,就依舊保持輕量級鎖的狀態,否則失敗執行緒進入阻塞狀態,將鎖的標誌位變為10,MarkWord變成指向重量級鎖的指標,成為重量級鎖

//Lock Record資料結構如下
class BasicObjectLock {
  friend class VMStructs;
 private:
  BasicLock _lock; 
  oop       _obj; 
};
class BasicLock {
 private:
  volatile markOop _displaced_header;
};

注意

只有兩條執行緒相互競爭的時候才會有輕量級鎖的產生,若是兩條以上的執行緒競爭,則直接膨脹為重量級鎖

重量級鎖

重量級鎖就是最傳統的鎖,只有一個執行緒獲得鎖,其他所有執行緒阻塞等待喚醒

總結

一般所說的偏向鎖->輕量級鎖->重量級鎖的流程只有在不使用HashCode且只有兩個執行緒競爭的時候生效

如果使用hashCode則會跳過偏向鎖,超過兩個執行緒競爭,則會直接成為重量級鎖.

相關文章