1. 程式人生 > >偏向鎖、輕量鎖、重量鎖的理解

偏向鎖、輕量鎖、重量鎖的理解

java中每個物件都可作為鎖,鎖有四種級別,按照量級從輕到重分為:無鎖、偏向鎖、輕量級鎖、重量級鎖。並且鎖只能升級不能降級。

在講這三個鎖之前,我先給大家講清楚自旋和物件頭的概念。

自旋

現在假設有這麼一個場景:有兩個執行緒A,B在競爭一個鎖,假設A拿到了,這個時候B被掛起阻塞,一直等待A釋放了鎖B才得到使用權。在作業系統中阻塞和喚醒是一個耗時操作,如果A在很短的時間內就釋放了鎖,當這個時間與阻塞喚醒比較起來更短的時候,我們將B掛起,其實不是一個最優的選擇。
自旋是指某執行緒需要獲取鎖,但該鎖已經被其他執行緒佔用時,該執行緒不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取鎖。雖然CPU的時間被消耗了,但是比執行緒下文切換時間要少。這個時候使用自旋是划算的。
如果是單核處理器,一般建議不要使用自旋鎖。因為只有單個處理器,自旋佔用的時間片使得代價很高。
而偏向鎖、輕量鎖、重量鎖也是一個鎖圍繞著如何使得程式執行的更加“划算”而進行改變的。

物件頭

HotSpot虛擬機器中,物件在記憶體中儲存的佈局可以分為三塊區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)。

HotSpot虛擬機器的物件頭(Object Header)包括兩部分資訊,第一部分用於儲存物件自身的執行時資料, 如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等等,這部分資料的長度在32位和64位的虛擬機器(暫 不考慮開啟壓縮指標的場景)中分別為32個和64個Bits,官方稱它為“Mark Word”。
在32位的HotSpot虛擬機器 中物件未被鎖定的狀態下,Mark Word的32個Bits空間中的25Bits用於儲存物件雜湊碼(HashCode),4Bits用於儲存物件分代年齡,2Bits用於儲存鎖標誌 位,1Bit固定為0,在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下物件的儲存內容如下表所示。
這裡寫圖片描述

偏向鎖

引入偏向鎖是為了在無多執行緒競爭的情況下儘量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令。
當只有一個執行緒去競爭鎖的時候,我們不需要阻塞,也不需要自旋,因為只有一個執行緒在競爭,我們只要去判斷該偏向鎖中的ThreadID是否為當前執行緒即可。如果是就執行同步程式碼,不是就嘗試使用CAS修改ThreadID,修改成功執行同步程式碼,不成功就將偏向鎖升級成輕量鎖。

輕量鎖

獲取輕量鎖的過程與偏向鎖不同,競爭鎖的執行緒首先需要拷貝物件頭中的Mark Word到幀棧的鎖記錄中。拷貝成功後使用CAS操作嘗試將物件的Mark Word更新為指向當前執行緒的指標。如果這個更新動作成功了,那麼這個執行緒就擁有了該物件的鎖。如果更新失敗,那麼意味著有多個執行緒在競爭。
當競爭執行緒嘗試佔用輕量級鎖失敗多次之後(使用自旋)輕量級鎖就會膨脹為重量級鎖,重量級執行緒指標指向競爭執行緒,競爭執行緒也會阻塞,等待輕量級執行緒釋放鎖後喚醒他。

重量鎖

重量級鎖的加鎖、解鎖過程和輕量級鎖差不多,區別是:競爭失敗後,執行緒阻塞,釋放鎖後,喚醒阻塞的執行緒,不使用自旋鎖,不會那麼消耗CPU,所以重量級鎖適合用在同步塊執行時間長的情況下。