java底層AQS實現(xiàn)類ReentrantLock鎖的構(gòu)成及源碼解析
引導語
本章的描述思路是先描述清楚 ReentrantLock 的構(gòu)成組件,然后使用加鎖和釋放鎖的方法把這些組件串起來。
1、類注釋
ReentrantLock 中文我們習慣叫做可重入互斥鎖,可重入的意思是同一個線程可以對同一個共享資源重復的加鎖或釋放鎖,互斥就是 AQS 中的排它鎖的意思,只允許一個線程獲得鎖。
我們來一起來看下類注釋上都有哪些重要信息:
- 可重入互斥鎖,和 synchronized 鎖具有同樣的功能語義,但更有擴展性;
- 構(gòu)造器接受 fairness 的參數(shù),fairness 是 ture 時,保證獲得鎖時的順序,false 不保證;
- 公平鎖的吞吐量較低,獲得鎖的公平性不能代表線程調(diào)度的公平性;
- tryLock() 無參方法沒有遵循公平性,是非公平的(lock 和 unlock 都有公平和非公平,而 tryLock 只有公平鎖,所以單獨拿出來說一說)。
我們補充一下第二點,ReentrantLock 的公平和非公平,是針對獲得鎖來說的,如果是公平的,可以保證同步隊列中的線程從頭到尾的順序依次獲得鎖,非公平的就無法保證,在釋放鎖的過程中,我們是沒有公平和非公平的說法的。
2、類結(jié)構(gòu)
ReentrantLock 類本身是不繼承 AQS 的,實現(xiàn)了 Lock 接口,如下:
public class ReentrantLock implements Lock, java.io.Serializable {}
Lock 接口定義了各種加鎖,釋放鎖的方法,接口有如下幾個:
// 獲得鎖方法,獲取不到鎖的線程會到同步隊列中阻塞排隊 void lock(); // 獲取可中斷的鎖 void lockInterruptibly() throws InterruptedException; // 嘗試獲得鎖,如果鎖空閑,立馬返回 true,否則返回 false boolean tryLock(); // 帶有超時等待時間的鎖,如果超時時間到了,仍然沒有獲得鎖,返回 false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 釋放鎖 void unlock(); // 得到新的 Condition Condition newCondition();
ReentrantLock 就負責實現(xiàn)這些接口,我們使用時,直接面對的也是這些方法,這些方法的底層實現(xiàn)都是交給 Sync 內(nèi)部類去實現(xiàn)的,Sync 類的定義如下:
abstract static class Sync extends AbstractQueuedSynchronizer {}
Sync 繼承了 AbstractQueuedSynchronizer ,所以 Sync 就具有了鎖的框架,根據(jù) AQS 的框架,Sync 只需要實現(xiàn) AQS 預留的幾個方法即可,但 Sync 也只是實現(xiàn)了部分方法,還有一些交給子類 NonfairSync 和 FairSync 去實現(xiàn)了,NonfairSync 是非公平鎖,F(xiàn)airSync 是公平鎖,定義如下:
// 同步器 Sync 的兩個子類鎖 static final class FairSync extends Sync {} static final class NonfairSync extends Sync {}
幾個類整體的結(jié)構(gòu)如下:
圖中 Sync、NonfairSync、FairSync 都是靜態(tài)內(nèi)部類的方式實現(xiàn)的,這個也符合 AQS 框架定義的實現(xiàn)標準。
3、構(gòu)造器
ReentrantLock 構(gòu)造器有兩種,代碼如下:
// 無參數(shù)構(gòu)造器,相當于 ReentrantLock(false),默認是非公平的 public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
無參構(gòu)造器默認構(gòu)造是非公平的鎖,有參構(gòu)造器可以選擇。
從構(gòu)造器中可以看出,公平鎖是依靠 FairSync 實現(xiàn)的,非公平鎖是依靠 NonfairSync 實現(xiàn)的。
4、Sync 同步器
Sync 表示同步器,繼承了 AQS,UML 圖如下:
從 UML 圖中可以看出,lock 方法是個抽象方法,留給 FairSync 和 NonfairSync 兩個子類去實現(xiàn),我們一起來看下剩余重要的幾個方法。
4.1、nonfairTryAcquire
// 嘗試獲得非公平鎖 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 同步器的狀態(tài)是 0,表示同步器的鎖沒有人持有 if (c == 0) { // 當前線程持有鎖 if (compareAndSetState(0, acquires)) { // 標記當前持有鎖的線程是誰 setExclusiveOwnerThread(current); return true; } } // 如果當前線程已經(jīng)持有鎖了,同一個線程可以對同一個資源重復加鎖,代碼實現(xiàn)的是可重入鎖 else if (current == getExclusiveOwnerThread()) { // 當前線程持有鎖的數(shù)量 + acquires int nextc = c + acquires; // int 是有最大值的,<0 表示持有鎖的數(shù)量超過了 int 的最大值 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //否則線程進入同步隊列 return false; }
以上代碼有三點需要注意:
通過判斷 AQS 的 state 的狀態(tài)來決定是否可以獲得鎖,0 表示鎖是空閑的;else if 的代碼體現(xiàn)了可重入加鎖,同一個線程對共享資源重入加鎖,底層實現(xiàn)就是把 state + 1,并且可重入的次數(shù)是有限制的,為 Integer 的最大值;這個方法是非公平的,所以只有非公平鎖才會用到,公平鎖是另外的實現(xiàn)。
無參的 tryLock 方法調(diào)用的就是此方法,tryLock 的方法源碼如下:
public boolean tryLock() { // 入?yún)?shù)是 1 表示嘗試獲得一次鎖 return sync.nonfairTryAcquire(1); }
4.2、tryRelease
// 釋放鎖方法,非公平和公平鎖都使用 protected final boolean tryRelease(int releases) { // 當前同步器的狀態(tài)減去釋放的個數(shù),releases 一般為 1 int c = getState() - releases; // 當前線程根本都不持有鎖,報錯 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果 c 為 0,表示當前線程持有的鎖都釋放了 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 如果 c 不為 0,那么就是可重入鎖,并且鎖沒有釋放完,用 state 減去 releases 即可,無需做其他操作 setState(c); return free; }
tryRelease 方法是公平鎖和非公平鎖都公用的,在鎖釋放的時候,是沒有公平和非公平的說法的。
從代碼中可以看到,鎖最終被釋放的標椎是 state 的狀態(tài)為 0,在重入加鎖的情況下,需要重入解鎖相應的次數(shù)后,才能最終把鎖釋放,比如線程 A 對共享資源 B 重入加鎖 5 次,那么釋放鎖的話,也需要釋放 5 次之后,才算真正的釋放該共享資源了。
5、FairSync 公平鎖
FairSync 公平鎖只實現(xiàn)了 lock 和 tryAcquire 兩個方法,lock 方法非常簡單,如下:
// acquire 是 AQS 的方法,表示先嘗試獲得鎖,失敗之后進入同步隊列阻塞等待 final void lock() { acquire(1); }
tryAcquire 方法是 AQS 在 acquire 方法中留給子類實現(xiàn)的抽象方法,F(xiàn)airSync 中實現(xiàn)的源碼如下:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // hasQueuedPredecessors 是實現(xiàn)公平的關鍵 // 會判斷當前線程是不是屬于同步隊列的頭節(jié)點的下一個節(jié)點(頭節(jié)點是釋放鎖的節(jié)點) // 如果是(返回false),符合先進先出的原則,可以獲得鎖 // 如果不是(返回true),則繼續(xù)等待 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 可重入鎖 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
代碼和 Sync 的 nonfairTryAcquire 方法實現(xiàn)類似,唯一不同的是在獲得鎖時使用 hasQueuedPredecessors 方法體現(xiàn)了其公平性。
6、NonfairSync 非公平鎖
NonfairSync 底層實現(xiàn)了 lock 和 tryAcquire 兩個方法,如下:
// 加鎖 final void lock() { // cas 給 state 賦值 if (compareAndSetState(0, 1)) // cas 賦值成功,代表拿到當前鎖,記錄拿到鎖的線程 setExclusiveOwnerThread(Thread.currentThread()); else // acquire 是抽象類AQS的方法, // 會再次嘗試獲得鎖,失敗會進入到同步隊列中 acquire(1); } // 直接使用的是 Sync.nonfairTryAcquire 方法 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
7、如何串起來
以上內(nèi)容主要說了 ReentrantLock 的基本結(jié)構(gòu),比較零散,那么這些零散的結(jié)構(gòu)如何串聯(lián)起來呢?我們是通過 lock、tryLock、unlock 這三個 API 將以上幾個類串聯(lián)起來,我們來一一看下。
7.1 lock 加鎖
lock 的代碼實現(xiàn):
public void lock() { sync.lock(); }
其底層的調(diào)用關系(只是簡單表明調(diào)用關系,并不是完整分支圖)如下:
7.2 tryLock 嘗試加鎖
tryLock 有兩個方法,一種是無參的,一種提供了超時時間的入?yún)?,兩種內(nèi)部是不同的實現(xiàn)機制,代碼分別如下:
// 無參構(gòu)造器 public boolean tryLock() { return sync.nonfairTryAcquire(1); } // timeout 為超時的時間,在時間內(nèi),仍沒有得到鎖,會返回 false public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
接著我們一起看下兩個 tryLock 的調(diào)用關系圖,下圖顯示的是無參 tryLock 的調(diào)用關系圖,如下:
我們需要注意的是 tryLock 無參方法底層走的就是非公平鎖實現(xiàn),沒有公平鎖的實現(xiàn)。
下圖展示的是帶有超時時間的有參 tryLock 的調(diào)用實現(xiàn)圖:
7.3 unlock 釋放鎖
unlock 釋放鎖的方法,底層調(diào)用的是 Sync 同步器的 release 方法,release 是 AQS 的方法,分成兩步:
嘗試釋放鎖,如果釋放失敗,直接返回 false;釋放成功,從同步隊列的頭節(jié)點的下一個節(jié)點開始喚醒,讓其去競爭鎖。
第一步就是我們上文中 Sync 的 tryRelease 方法(4.1),第二步 AQS 已經(jīng)實現(xiàn)了。
unLock 的源碼如下:
// 釋放鎖 public void unlock() { sync.release(1); }
7.4 Condition
ReentrantLock 對 Condition 并沒有改造,直接使用 AQS 的 ConditionObject 即可。
8、總結(jié)
這就是我們在研究完 AQS 源碼之后,碰到的第一個鎖,是不是感覺很簡單,AQS 搭建了整個鎖架構(gòu),子類鎖只需要根據(jù)場景,實現(xiàn) AQS 對應的方法即可,不僅僅是 ReentrantLock 是這樣,JUC 中的其它鎖也都是這樣,只要對 AQS 了如指掌,鎖其實非常簡單。
以上就是java底層AQS實現(xiàn)類kReentrantLock鎖的構(gòu)成及源碼解析的詳細內(nèi)容,更多關于AQS實現(xiàn)類ReentrantLock鎖的構(gòu)成的資料請關注腳本之家其它相關文章!
相關文章
spring boot整合Shiro實現(xiàn)單點登錄的示例代碼
本篇文章主要介紹了spring boot整合Shiro實現(xiàn)單點登錄的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01SSM框架中entity mapper dao service controll
這篇文章主要介紹了SSM框架中entity mapper dao service controller層的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11詳解在idea 中使用Mybatis Generator逆向工程生成代碼
這篇文章主要介紹了在idea 中使用Mybatis Generator逆向工程生成代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12Java封裝數(shù)組實現(xiàn)在數(shù)組中查詢元素和修改元素操作示例
這篇文章主要介紹了Java封裝數(shù)組實現(xiàn)在數(shù)組中查詢元素和修改元素操作,結(jié)合實例形式分析了java針對數(shù)組元素查詢、修改的封裝操作實現(xiàn)技巧,需要的朋友可以參考下2020-03-03SpringBoot整合Keycloak實現(xiàn)單點登錄的示例代碼
本文主要介紹了SpringBoot整合Keycloak實現(xiàn)單點登錄的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03