java併發之——偏向鎖、輕量級鎖、重量級鎖的升級過程與原理


一、Synchronized實現原理

1、Synchronized鎖的3中形式:

Synchronized修飾普通同步方法:鎖對象當前實例對象;

Synchronized修飾靜態同步方法:鎖對象是當前的類Class對象;

Synchronized修飾同步代碼塊:鎖對象是Synchronized後面括號裡配置的對象;

2、Synchronized在JVM裡的實現

那麼Synchronized在JVM裡是怎麼實現的呢?

在JVM規範裡可以看到Synchronized鎖時基於進入和退出monitor對象來實現方法同步和代碼塊同步,這兩種同步的具體實現細節不太一樣,但是都可以這麼理解:不管是方法同步還是代碼塊同步都是通過monitor對象的monitorentry和monitorexit這兩個指令實現的,在需要同步的代碼塊開始的位置插入monitorentry指令,在同步結束的位置或者異常出現的位置插入monitorexit指令;JVM要保證monitorentry和monitorexit都是成對出現的,任何對象都有一個monitor閾值對應,當這個對象的monitor被持有以後,它將處於鎖定狀態。

3、Java對象頭

那麼Synchronized鎖對象是存在哪裡的呢?答案是存在鎖對象的對象頭中,稱為MarkWord。如果對象是數組對象,那麼MarkWord佔用3個字寬(Word),如果對象是非數組對象,那麼MarkWord佔用2個字寬。(1word = 2 Byte = 16 bit)

那麼MarkWord在對象頭中到底長什麼樣,也就是它到底存儲了什麼呢?

在32位的虛擬機中:


java併發之——偏向鎖、輕量級鎖、重量級鎖的升級過程與原理


在64位的虛擬機中:


java併發之——偏向鎖、輕量級鎖、重量級鎖的升級過程與原理


二、鎖升級

1、鎖的4中狀態:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態(級別從低到高)

2、偏向鎖:

為什麼要引入偏向鎖?

因為經過HotSpot的作者大量的研究發現,大多數時候是不存在鎖競爭的,常常是一個線程多次獲得同一個鎖,因此如果每次都要競爭鎖會增大很多沒有必要付出的代價,為了降低獲取鎖的代價,才引入的偏向鎖。

偏向鎖的升級

當線程1訪問代碼塊並獲取鎖對象時,會在java對象頭和棧幀中記錄偏向的鎖的threadID,因為偏向鎖不會主動釋放鎖,因此以後線程1再次獲取鎖的時候,需要比較當前線程的threadID和Java對象頭中的threadID是否一致,如果一致(還是線程1獲取鎖對象),則無需使用CAS來加鎖、解鎖;如果不一致(其他線程,如線程2要競爭鎖對象,而偏向鎖不會主動釋放因此還是存儲的線程1的threadID),那麼需要

查看Java對象頭中記錄的線程1是否存活,如果沒有存活,那麼鎖對象被重置為無鎖狀態,其它線程(線程2)可以競爭將其設置為偏向鎖;如果存活,那麼立刻查找該線程(線程1)的棧幀信息,如果還是需要繼續持有這個鎖對象,那麼暫停當前線程1,撤銷偏向鎖,升級為輕量級鎖,如果線程1 不再使用該鎖對象,那麼將鎖對象狀態設為無鎖狀態,重新偏向新的線程。

偏向鎖的取消:

偏向鎖是默認開啟的,而且開始時間一般是比應用程序啟動慢幾秒,如果不想有這個延遲,那麼可以使用-XX:BiasedLockingStartUpDelay=0;

如果不想要偏向鎖,那麼可以通過-XX:-UseBiasedLocking = false來設置;

3、輕量級鎖

為什麼要引入輕量級鎖?

輕量級鎖考慮的是競爭鎖對象的線程不多,而且線程持有鎖的時間也不長的情景。因為阻塞線程需要CPU從用戶態轉到內核態,代價較大,如果剛剛阻塞不久這個鎖就被釋放了,那這個代價就有點得不償失了,因此這個時候就乾脆不阻塞這個線程,讓它自旋這等待鎖釋放。

輕量級鎖什麼時候升級為重量級鎖?

線程1獲取輕量級鎖時會先把鎖對象的對象頭MarkWord複製一份到線程1的棧幀中創建的用於存儲鎖記錄的空間(稱為DisplacedMarkWord),然後使用CAS把對象頭中的內容替換為線程1的鎖記錄地址

如果在線程1複製對象頭的同時(在線程1CAS之前),線程2也準備獲取鎖,複製了對象頭到線程2的鎖記錄空間中,但是在線程2CAS的時候,發現線程1已經把對象頭換了,線程2的CAS失敗,那麼線程2就嘗試使用自旋鎖來等待線程1釋放鎖

但是如果自旋的時間太長也不行,因為自旋是要消耗CPU的,因此自旋的次數是有限制的,比如10次或者100次,如果自旋次數到了線程1還沒有釋放鎖,或者線程1還在執行,線程2還在自旋等待,這時又有一個線程3過來競爭這個鎖對象,那麼這個時候輕量級鎖就會膨脹為重量級鎖。重量級鎖把除了擁有鎖的線程都阻塞,防止CPU空轉。

*注意:為了避免無用的自旋,輕量級鎖一旦膨脹為重量級鎖就不會再降級為輕量級鎖了;偏向鎖升級為輕量級鎖也不能再降級為偏向鎖。一句話就是鎖可以升級不可以降級,但是偏向鎖狀態可以被重置為無鎖狀態。

4、這幾種鎖的優缺點(偏向鎖、輕量級鎖、重量級鎖)

java併發之——偏向鎖、輕量級鎖、重量級鎖的升級過程與原理


最後總結:

偏向鎖適合單線程的場景,這樣線程每次進入同步方法,直接比較對應的markword中的線程ID是否是自己就行了,若是繼續執行若不是則這個偏向鎖的所有者是否已經結束,若結束則佔有偏向鎖,若沒有結束則將該鎖升級成輕量級鎖;

若競爭輕量級鎖,若輕量級鎖已經被佔用則將輕量級鎖升級成重量級鎖然後阻塞,若沒有被佔用則直接獲取輕量級鎖(這裡需要注意若兩個線程同時獲取一個沒有被佔用的輕量級鎖時,其中一個會獲取成功另一個會自旋,若自旋一段時間仍未獲取鎖則會將輕量級鎖升級成重量級鎖)

輕量級鎖持有者解鎖時,若發現自己保存的markword和目前的markword不一致(輕量級鎖持有者在獲取的時候markword標記的還是輕量級鎖,後來由於其他線程的申請鎖造成所升級成重量級鎖,則markword會改變)則認為有線程在申請鎖,則會喚醒阻塞的進程,讓他們重新申請;


分享到:


相關文章: