天天看點

ReentrantLock可重入鎖—源碼詳解

ReentrantLock可重入鎖—源碼詳解

結合AQS,解析JUC可重入鎖——ReentrantLock源碼

開始這篇部落格之前,部落客預設大家都是看過AQS源碼的~什麼居然沒看過🤬猛戳下方👇👇👇 全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析(一)AQS基礎 全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析(二)資源的擷取和釋放 全網最詳細的AbstractQueuedSynchronizer(AQS)源碼剖析(三)條件變量

<code>ReentrantLock</code>是可重入鎖,是JUC提供的一種最常用的鎖。“可重入”的意思就是:同一個線程可以無條件地反複獲得已經持有的鎖

<code>ReentrantLock</code>有公平鎖和非公平鎖兩種模式,底層使用的正是<code>AbstractQueuedSynchronizer</code>這個偉大的并發工具

<code>ReentrantLock</code>的結構如下圖所示:

ReentrantLock可重入鎖—源碼詳解

<code>ReentrantLock</code>實作了<code>Lock</code>接口,該接口定義了一個鎖應該具備的基本功能,即加鎖、解鎖、建立條件變量等功能。源碼如下:

使用<code>ReentrantLock</code>,主要使用<code>lock</code>和<code>unlock</code>方法,也會用到<code>newCondition</code>來建立條件變量,實作一些條件同步功能

回到上面的結構圖,可以看到,<code>ReentrantLock</code>的功能主要是借助其内部類<code>Sync</code>來實作,而<code>Sync</code>類是繼承了<code>AbstractQueuedSynchronizer</code>,并衍生出兩個子類<code>FairSync</code>、<code>NonfairSync</code>,分别對應公平鎖和非公平鎖兩種模式。實際應用中,一般非公平鎖的效率要高于公平鎖。具體原因見最後一節“相關面試題"

作者:酒冽        出處:https://www.cnblogs.com/frankiedyz/p/15719681.html

版權:本文版權歸作者和部落格園共有

轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任

<code>ReentrantLock</code>中的<code>sync</code>域是是一個<code>Sync</code>類對象,<code>ReentrantLock</code>使用<code>sync</code>來實作主要的功能。<code>Sync</code>類是<code>ReentrantLock</code>的内部類:

<code>Sync</code>類繼承了AQS,使用AQS的<code>state</code>作為鎖的重入數,其源碼如下:

總結一下,<code>Sync</code>類有以下幾個作用:

定義抽象<code>lock</code>方法:<code>Sync</code>定義了<code>lock</code>這一個抽象方法,強迫兩個子類去實作,而<code>ReentrantLock</code>的<code>lock</code>方法就可以直接委托給<code>Sync</code>的<code>lock</code>方法去執行

非公平模式的嘗試擷取鎖:<code>Sync</code>提供了<code>nonfairTryAcquire</code>方法,提供非公平嘗試擷取鎖的方法。不僅非公平子類<code>NonfairSync</code>實作<code>tryAcquire</code>方法需要委托給<code>nonfairTryAcquire</code>來處理,而且<code>ReentrantLock</code>中的<code>tryLock</code>方法也會委托給它來處理

嘗試釋放鎖:<code>Sync</code>實作了AQS中的<code>tryRelease</code>方法,因為不管是公平模式還是非公平模式,釋放鎖的邏輯都是相同的,是以在<code>Sync</code>這一層就提供了具體的實作,而沒有下放給子類來實作

條件變量的支援:<code>Sync</code>實作了AQS中的<code>isHeldExclusively</code>方法(該方法會被AQS中的<code>ConditionObject</code>的<code>signal</code>方法調用),并提供了<code>newCondition</code>方法建立條件變量

擷取鎖資訊的方法:<code>Sync</code>提供了<code>getOwner</code>、<code>getHoldCount</code>、<code>isLocked</code>三個方法用于擷取鎖的資訊,外圍類<code>ReentrantLock</code>的三個同名方法會委托這三個方法來執行

要利用AQS實作擷取鎖的功能,需要實作<code>tryAcquire</code>方法。但是由于公平模式和非公平模式下擷取鎖的邏輯不同,是以<code>tryAcquire</code>交給兩個子類去實作,<code>Sync</code>并不實作

但是對于非公平模式的擷取鎖,<code>NonFairSync</code>子類實作的<code>tryAcquire</code>方法實際上委托了<code>Sync</code>類的<code>nonfairTryAcquire</code>方法來處理。<code>nonfairTryAcquire</code>的源碼分析放在後面的非公平模式去講解

要利用AQS實作釋放鎖的功能,需要實作<code>tryRelease</code>方法。不同于擷取鎖,對于公平模式和非公平模式來說,釋放鎖的邏輯是相同的,是以<code>tryRelease</code>的實作直接交給<code>Sync</code>這一層來實作,而沒有下放給子類來實作

<code>tryRelease</code>是嘗試釋放資源,而在<code>ReentrantLock</code>中的語義環境下就是嘗試釋放鎖。其源碼如下:

<code>tryRelease</code>的<code>releases</code>參數說明: 由于每次隻釋放一個鎖,是以調用<code>lock</code>釋放鎖時<code>tryRelease</code>的<code>releases</code>參數恒為1 但是<code>ReentrantLock</code>支援條件變量,條件變量的<code>await</code>方法也會調用<code>tryRelease</code>方法一次性釋放所有的鎖資源,此時<code>tryRelease</code>的參數<code>releases</code>不一定為1

AQS中的<code>release</code>方法會調用<code>tryRelease</code>方法并接收其傳回值,如下:

如果<code>tryRelease</code>傳回true,說明鎖為空閑,那麼就需要喚醒等待擷取鎖而阻塞,且等待最久的線程,讓它來擷取鎖。是以<code>release</code>會喚醒同步隊列的隊首線程。如果鎖不是空閑,就不需要喚醒任何線程

非公平鎖是借助<code>Sync</code>和其子類<code>NonfairSync</code>來實作的。<code>NonfairSync</code>實作了<code>Sync</code>定義的<code>lock</code>抽象方法,以及實作了AQS中的<code>tryAcquire</code>方法以擷取鎖。源碼如下:

<code>lock</code>方法先直接CAS修改<code>state</code>,如果鎖空閑且修改成功,則說明擷取到了鎖,這裡也展現出非公平性,因為它不會謙讓已經在同步隊列中等待的線程

如果鎖非空閑或者競争失敗,則會調用<code>acquire</code>方法。<code>acquire</code>會調用非公平鎖實作的<code>tryAcquire</code>方法,再次進行競争,可能直接擷取到鎖,也可能再次失敗,進入同步隊列阻塞等待,這裡同樣展現了非公平性

非公平鎖實作的<code>tryAcquire</code>實際委托<code>Sync.nonfairTryAcquire</code>方法來執行,該方法源碼如下:

第一個<code>if</code>展現了該方法的非公平性,擷取鎖的線程不會給同步隊列的隊首線程“謙讓”,而是直接上去CAS競争,如果競争成功,将比隊首線程更先獲得鎖,這展現了不公平性

<code>ReentrantLock</code>預設建立出來的是非公平鎖,因為非公平鎖的效率一般要高于公平鎖:

公平鎖是借助<code>Sync</code>和其子類<code>FairSync</code>來實作的。<code>FairSync</code>實作了<code>Sync</code>定義的<code>lock</code>抽象方法,以及實作了AQS中的<code>tryAcquire</code>方法以擷取鎖。源碼如下:

<code>lock</code>方法直接調用<code>acquire</code>方法擷取鎖,而<code>acquire</code>會調用非公平鎖實作的<code>tryAcquire</code>方法,而<code>tryAcquire</code>也遵循公平性,是以該<code>lock</code>方法整體上就是公平的

<code>tryAcquire</code>方法會檢查鎖是否空閑,如果空閑,也不會立即去CAS争奪,而是調用AQS的<code>hasQueuedPredecessors</code>方法檢查是否有線程在同步隊列中等待,如果沒有才會CAS競争。如果有就說明不能競争,傳回false

AQS中的<code>hasQueuedPredecessors</code>方法會檢查是否有線程在同步隊列中等待,源碼如下:

要使用公平模式的鎖,需要将`ReentrantLock`的構造參數`fair`設為true。如果是false或不設定,則建立的都是非公平模式的鎖:

<code>ReentrantLock</code>實作了<code>Lock</code>接口的所有方法,如下:

可以看到,<code>ReentrantLock</code>實作的所有<code>Lock</code>方法其實都是委托給了<code>Sync</code>(AQS)來執行

<code>ReentrantLock</code>是如何實作可重入的

無論是公平鎖還是非公平鎖,擷取鎖調用<code>tryAcquire</code>方法時,擷取成功後都會設定目前持有鎖的線程是自己。如果再次擷取該鎖,當發現鎖已經被持有時,會判斷持有鎖的線程是否是自己,如果是就可以不用競争而直接擷取鎖

簡述非公平鎖和公平鎖之間的差別

從定義角度來說:

公平鎖:擷取鎖的順序和請求鎖的順序是一緻的,即誰申請得早(等待得久),誰就最先擷取鎖

非公平鎖:競争鎖時,等待時間最長的線程和剛剛過來競争鎖(不在阻塞同步隊列中)的線程都有可能擷取鎖,CPU時間片輪詢到哪個線程,哪個就能獲得鎖

從源碼角度來說:

當鎖被占用時,請求鎖的所有線程都會按照FIFO的順序在同步隊列中阻塞等待。在鎖被釋放的時候,如果是非公平鎖,則隊首線程和剛剛過來請求鎖而不在阻塞隊列中的線程,都可能獲得鎖。如果是公平鎖,就一定是隊首線程獲得鎖,剛剛過來請求鎖得線程會被加入同步隊列阻塞等待

從效率上來說:公平鎖效率低于非公平鎖,主要是兩方面的開銷

代碼執行上的開銷:公平模式下會多執行一個方法,該方法用于判斷是否有其他線程正在同步隊列中等待

系統層面的開銷:公平模式下,隊首線程是阻塞的,是以必須先将隊首線程喚醒,這涉及到作業系統上下文切換的操作,開銷較大。而在非公平模式下,可能是剛剛過來請求鎖的線程獲得鎖,而該線程已經是喚醒狀态,不需要上下文切換

為什麼<code>ReentrantLock.lock</code>方法不能被其他線程中斷

因為<code>lock</code>方法調用的是AQS中的<code>acquire</code>方法,該方法忽略中斷。而<code>acquire</code>方法又會調用<code>acquireQueued</code>方法,該方法執行過程中如果有其他線程中斷了目前線程,隻會将中斷記錄下來,不會響應中斷。如果鎖已經被擷取,那麼該線程需要被阻塞,阻塞調用的是<code>LockSupport.unpark</code>方法,該方法接收到中斷信号後,不會抛出中斷異常,而是傳回。傳回之後又會進入<code>acquireQueued</code>的循環,如果不是隊首,就重新被阻塞。是以整個過程都不會被其他線程中斷,隻會将中斷記錄下來

<code>ReentrantLock</code>與<code>synchronized</code>之間的相同和不同點

相同點:

它們都是通過加鎖實作同步,而且都是阻塞式同步,而不是非阻塞式(自旋鎖),即當一個線程擷取鎖後,其他線程再請求鎖就會失敗而被阻塞,等到鎖釋放才有機會被喚醒

不同點:

<code>ReentrantLock</code>:是Java 5之後提供的API層面的互斥鎖;需要<code>lock</code>、<code>unlock</code>配合<code>try</code>、<code>finally</code>使用;支援定時擷取鎖功能;支援可中斷的加鎖方法<code>lockInterruptibly</code>,在等待擷取鎖時響應中斷,會抛出中斷異常

<code>synchronized</code>:是Java語言的關鍵字,通過JVM實作;使用便捷;不支援定時擷取鎖功能;<code>synchronized</code>在等待擷取鎖時不響應中斷,不抛出中斷異常,隻記錄中斷狀态

公平鎖和非公平鎖之間最大的差別在哪裡(一句話版本)?

公平鎖:先到臨界區的線程一定會比後到的先獲得鎖

非公平鎖:先到臨界區的線程不一定比後到的先獲得鎖

<code>synchronized</code>加鎖是公平鎖還是非公平鎖?

<code>synchronized</code>是非公平鎖

如果使用<code>synchronized</code>鎖,線上程到達臨界區時就直接CAS嘗試擷取鎖,如果失敗則更新為輕量級鎖,再不斷CAS請求鎖。當CAS失敗到達一定次數之後,更新為重量級鎖,放入monitor對象的隊列中阻塞等待。而且入隊之前也會先嘗試擷取鎖,擷取不到才進入等待隊列

是以,線程擷取<code>synchronized</code>鎖都不會關心有沒有其他線程之前擷取過,是以<code>synchronized</code>是非公平鎖

為什麼要設定前驅節點的狀态為<code>SIGNAL</code>?

為了表示前驅節點的後繼節點對應的線程需要被喚醒,就這麼簡單

願歸來仍是少年!

    作者:酒冽

    出處:https://www.cnblogs.com/frankiedyz/p/15719681.html

    版權:本文版權歸作者和部落格園共有

    轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任