1. 程式人生 > 其它 >鎖升級 鎖降級

鎖升級 鎖降級

首先說明一下,鎖升級和鎖降級說的根本不是一個事情,鎖升級是synchronized關鍵字在jdk1.6之後做的優化,鎖降級是為了保證資料的可見性在添加了寫鎖後再新增一道讀鎖,鎖降級請參考連結1。本文主要針對鎖升級介紹。

一、鎖升級

之前介紹過synchronized關鍵字,synchronized關鍵字可以鎖類,鎖方法和鎖程式碼塊,有關synchronized關鍵字的使用可以參考連結2synchronized鎖一致被認為是比較重量級的鎖,但JDK1.6之後對synchronized鎖是有優化的,本文詳細介紹一下JDK1.6之後synchronized鎖的底層實現原理。

1Java物件在堆記憶體的構成

JVM中,物件在堆記憶體中分為三塊區域:

1)物件頭

物件頭相當於物件的元資料資訊,物件頭由兩部分組成:

aMark Word(標記欄位)

儲存物件的HashCode、分代年齡和鎖標誌位資訊,在執行期間Mark Word裡儲存的資料結構會隨著鎖標誌位的變化而變化,Mark Word的結構圖如下,圖摘自連結3

上面提到了Mark Word被設計成一個非固定結構,在執行期間會隨著鎖標誌位的變化而變化,上圖中一個鎖標誌位所在的一行資料結構就對應一種Mark Word結構。

bKlass Pointer(型別指標)

物件指向它的類元資料的指標,JVM通過這個指標來確定物件是哪個類的例項。

2)例項資料

這部分主要存放類的資料資訊和父類資訊。

3)填充資料

JVM要求物件的起始地址必須是8位元組的整數倍,填充資料不是必須存在的,僅僅是為了位元組對齊。

2synchronized/重量級鎖

這裡主要介紹一下通常說的synchronized鎖或者重量級鎖的底層實現原理

2.1 Monitor物件

我們經常說synchronized關鍵字獲得的是一個物件鎖,那這個物件鎖到底是什麼?

每一個物件的物件頭會關聯一個Monitor物件,這個Monitor物件的實現底層是用C++寫的,對應在虛擬機器裡的ObjectMonitor.hpp檔案中。

Monitor物件由以下3部分組成:

1EntryList佇列

當多個執行緒同時訪問一個Monitor物件時,這些執行緒會先被放進EntryList佇列,此時這些執行緒處於Blocked狀態;

2Owner

當一個執行緒獲取到了這個Monitor物件時,Owner會指向這個執行緒,當執行緒釋放掉了Monitor物件時,Owner會置為null

3WaitSet佇列

當執行緒呼叫wait方法時,當前執行緒會釋放物件鎖,同時該執行緒進入WaitSet佇列。

Monitor物件還有一個計數器count的概念,這個count是屬於Monitor物件的,而不屬於某個獲得了Monitor物件的執行緒,當Monitor物件被某個執行緒獲取時,++count,當Monitor物件被某個執行緒釋放時,--count

2.2 同步程式碼塊和同步方法

synchronized關鍵字可以修飾方法,也可以修飾程式碼塊,二者底層的實現稍有不同。

1)同步程式碼塊

public void method(){

        synchronized(new Object()){

          do something...

        }

    }

    當進入method方法的synchronized程式碼塊時,通過monitorenter指令獲得Monitor物件的所有權,此時count+1Monitor物件的owner指向當前執行緒;如果當前執行緒已經是Monitor物件的owner了,再次進入synchronized程式碼塊時,會將count+1;當執行緒執行完synchronized程式碼塊裡的內容後,會執行monitorexit,對應的count-1,直到count0時,才認為Monitor物件不再被執行緒佔有,其他執行緒才可以嘗試獲取Monitor物件。

2)同步方法

當執行緒呼叫到方法時,會判斷一個標誌位:ACC_SYNCHRONIZED。當方法是同步方法時,會有這個標誌位,ACC_SYNCHRONIZED會去隱式呼叫那兩個指令:monitorentermonitorexit去獲得和釋放Monitor物件。

歸根到底,synchronized關鍵字還是看哪個執行緒獲得了物件對應的Monitor物件。

3、鎖升級過程

JDK1.6之前,synchronized的實現涉及到作業系統實現執行緒之間的切換時需要從使用者態切換為核心態,這是很消耗資源的,這也是早期synchronized鎖稱為“重量級”鎖的原因,jdk1.6之後對synchronized鎖進行了優化,引入了偏向鎖和輕量級鎖的概念,即synchronized鎖有具體4種狀態,這幾個狀態會隨著競爭程度逐漸升級,就是鎖升級。

3.1 synchronized鎖的4種狀態

synchronized鎖有無鎖、偏向鎖、輕量級鎖和重量級鎖4種狀態,在物件頭的Mark Word裡有展示,鎖狀態不同,Mark Word的結構也不同。

1)無鎖

很好理解,就是不存在競爭,執行緒沒有獲取synchronized鎖的狀態。

2)偏向鎖

即偏向第一個拿到鎖的執行緒,鎖會在物件頭的Mark Word通過CASCompare And Swap)記錄獲得鎖的執行緒id,同時將Mark Word裡的鎖狀態置為偏向鎖,是否為偏向鎖的位也置為1,當下一次還是這個執行緒獲取鎖時就不需要通過CAS

如果其他的執行緒嘗試通過CAS獲取鎖(即想將物件頭的Mark Word中的執行緒ID改成自己的)會獲取失敗,此時鎖由偏向鎖升級為輕量級鎖。

3)輕量級鎖

JVM會給執行緒的棧幀中建立一個鎖記錄(Lock Record)的空間,將物件頭的Mark Word拷貝到Lock Record中,並嘗試通過CAS把原物件頭的Mark Word中指向鎖記錄的指標指向當前執行緒中的鎖記錄,如果成功,表示執行緒拿到了鎖。如果失敗,則進行自旋(自旋鎖),自旋超過一定次數時升級為重量級鎖,這時該執行緒會被核心掛起。

4)自旋鎖

輕量級鎖升級為重量級鎖之前,執行緒執行monitorenter指令進入Monitor物件的EntryList佇列,此時會通過自旋嘗試獲得鎖,如果自旋次數超過了一定閾值(預設10),才會升級為重量級鎖,等待執行緒被喚起。

執行緒等待喚起的過程涉及到Linux系統使用者態和核心態的切換,這個過程是很消耗資源的,自選鎖的引入正是為了解決這個問題,先不讓執行緒立馬進入阻塞狀態,而是先給個機會自旋等待一下。

5)重量級鎖

2中已經介紹,就是通常說的synchronized重量級鎖。

3.2 鎖升級過程

鎖升級的順序為:

無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖,且鎖升級的順序是不可逆的。

執行緒第一次獲取鎖獲時鎖的狀態為偏向鎖,如果下次還是這個執行緒獲取鎖,則鎖的狀態不變,否則會升級為CAS輕量級鎖;如果還有執行緒競爭獲取鎖,如果執行緒獲取到了輕量級鎖沒啥事了,如果沒獲取到會自旋,自旋期間獲取到了鎖沒啥事,超過了10次還沒獲取到鎖,鎖就升級為重量級的鎖,此時如果其他執行緒沒獲取到重量級鎖,就會被阻塞等待喚起,此時效率就低了。

具體順序如圖所示,圖摘自連結4