java中的synchronized只是重量級鎖嗎?聊一聊synchronized鎖升級流程


synchronized這個關鍵字,原來的印象就是一個重量級鎖,也就是悲觀鎖,直接鎖住代碼段,剩余的線程進入到阻塞隊列中,效率極低,實際上呢,在jdk1.6之后,synchronized的內部進行了優化,它不再是一個簡單的重量級鎖,它為了試用所有的情況,有了一個鎖升級流程:無鎖 -》 偏向鎖  -》 輕量級鎖 -》 重量級鎖,接下來我們仔細的聊一下所謂的鎖升級流程。

 

首先,現來看一下,synchronized的使用方法:

1、對一個對象進行加鎖

synchronized(this){
    //代碼
}

2、對一個方法進行加鎖

public synchornized void test(){
    //代碼
}

實際上,無論是對一個對象進行加鎖還是對一個方法進行加鎖,實際上,都是對對象進行加鎖。也就是說,對於方式2,實際上虛擬機會根據synchronized修飾的是實例方法還是類方法,去取對應的實例對象或者Class對象來進行加鎖。

所以既然是對對象加鎖,我們是不是應該了解下對象的結構呢,樓主的上一篇文章有關於對象結構的,不再多說。鎖的信息都是存在對象頭中的MarkWord中的。結構如下:

 

 一、無鎖狀態

當一個對象被創建出來時,為無鎖狀態,所標記位位01,是否偏向鎖位0。

 

二、偏向鎖

當一個線程執行到鎖相關的代碼段時,就會將是否偏向鎖置為1,此時MarkWord結構如下:

bit fields   是否偏向鎖 鎖標志位
threadId epoch 1 01

 

 

 

此時有線程占據了這個鎖,這也就是偏向鎖。

這也就到了偏向鎖,這個鎖會偏向於第一個獲得它的線程,在接下來的執行過程中,假如該鎖沒有被其他線程所獲取,沒有其他線程來競爭該鎖,那么持有偏向鎖的線程將永遠不需要進行同步操作。

假如,這時候來了一個線程,也來搶占該鎖,處理流程如下:

1、判斷線程id是否和MarkWord中的線程id相同,如果相同,那么直接執行代碼塊,否則執行下一步。

2、查看對象是否為可偏向,如果為0,那么進行CAS操做,將MarkWord中的否偏向鎖置為1,並記錄id。如果不是,執行下一步驟。

3、這時候就出現了竟爭鎖的情況,新線程,會嘗試CAS操作,來更新線程id,如果失敗,就進行鎖的撤銷或升級為輕量級鎖。

 

如果失敗,就去撤銷鎖,首先需要判斷,MarkWord中存放的線程id是否還存活,如果已經死亡,就撤銷鎖,然后新線程,獲取鎖,否則升級為輕量級鎖。

 

其中簡單聊一下撤銷鎖:

鎖的對象頭中偏向着線程1,因為它不知道線程1什么時候來,所以一直偏向着,就算線程1已經死亡了。所以撤銷鎖的時候,先檢查對象頭所指向的線程是否存活,如果不存活,那么偏向鎖撤銷為無鎖,線程2就拿到了鎖,如果存在,那么線程1目前沒有拿着鎖而在干別的事情,這樣鎖就在不同時間段被不同線程訪問了升級為輕量級鎖。

 

所以,如果程序肯定是兩個線程竟爭,我們可以一開始就把偏向鎖這個默認功能給關閉,否則浪費大量的資源。

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

三、輕量級鎖

簡單描述下偏向鎖升級為輕量級鎖的過程:

1、線程在自己的線程棧中,新建一個鎖記錄LockRecord,官方稱為Displaced Mark Word。

2、將LockRecord中的對象指針,指向鎖對象,然后將鎖對象的MarkWord復制到LockRecord中。

3、將鎖對象的MarkWord替換為為指向LockRecord的指針。

為什么要拷貝mark word?
原因是為了不想在lock與unlock這種底層操作上再加同步,如果每個線程進來都不拷貝,直接對內容進行更改的話,可能會出錯。

將LockRecord中的對象指針,指向鎖對象,是為了識別,哪個對象被鎖住了。

將鎖對象的MarkWord替換為為指向LockRecord的指針,是為了讓其他線程知道,這個鎖被獲取了。

然后MarkWord更新為:

bit fields 鎖標志位
指向LockRecord的指針 00

 

 

 

然后簡單聊聊輕量級鎖。

輕量級鎖分為自旋鎖和自適應自旋鎖

1、自旋鎖

自旋鎖也就是當一個線程占據着鎖時,這時候另一個線程來了,發現鎖被占用,就開始進行不停的嘗試CAS操作,也就是不停的執行for循環,來不停的嘗試如果線程嘗試獲取鎖的時候,輕量鎖正被其他線程占有,就會不停的自旋獲取鎖,如果超過次數獲取鎖,那么它就會修改MarkWord,修改重量級鎖,表示該進入重量鎖了,知道獲取鎖之后,結束。

當線程太多之后,就會出現一個問題,假如有100個線程竟爭資源,有99個在不停的執行for循環,這個cpu的消耗是非常可怕的。所以,線程太多了,就需要讓線程阻塞,然后執行了,默認情況下,自旋的次數為10次,用戶可以通過-XX:PreBlockSpin來進行更改,為了優化引入了自適應自旋鎖,超過了次數,就升級為重量級鎖。

2、自適應自旋(此操作為了防止長時間的自旋,在自旋操作上加了一些限制條件)。

比如一開始給線程自旋的時間是10秒,如果線程在這個時間內獲得了鎖,那么就認為這個線程比較容易獲得鎖,就會適當的加長它的自旋時間。

如果這個線程在規定時間內沒有獲得到鎖,並且阻塞了。那么就認為這個線程不容易獲得鎖,下次當這個線程進行自旋的時候會減少它的自旋時間。

 

輕量級鎖解鎖操作

1、輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭。

2、如果成功,則表示沒有競爭發生。成功替換,等待下一個線程獲取鎖。

3、如果失敗,表示當前鎖存在競爭(因為自旋失敗的線程已經將對象頭中的輕量級鎖00改變為了10),鎖就會膨脹成重量級鎖。

4、因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

 

四、重量級鎖

升級為重量級鎖之后,MarkWord中發生變化,如下:

bit fields 鎖標志位
指向Mutex的指針 10

 

 

 

重量級鎖是依賴對象內部的monitor鎖來實現的,而monitor又依賴操作系統的MutexLock(互斥鎖)來實現的,所以重量級鎖也被成為互斥鎖。

重量級鎖的工作流程如下:當系統檢查到鎖是重量級鎖之后,會把等待想要獲得鎖的線程進行阻塞,被阻塞的線程不會消耗cup。但是阻塞或者喚醒一個線程時,都需要操作系統來幫忙,這就需要從用戶態轉換到內核態,而轉換狀態是需要消耗很多時間的,有可能比用戶執行代碼的時間還要長。所以重量級鎖的開銷還是很大的。

 

這就是synchronized中的鎖升級流程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM