java底層AQS實現(xiàn)類ReentrantLock鎖的構(gòu)成及源碼解析
引導(dǎo)語
本章的描述思路是先描述清楚 ReentrantLock 的構(gòu)成組件,然后使用加鎖和釋放鎖的方法把這些組件串起來。
1、類注釋
ReentrantLock 中文我們習慣叫做可重入互斥鎖,可重入的意思是同一個線程可以對同一個共享資源重復(fù)的加鎖或釋放鎖,互斥就是 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 預(yù)留的幾個方法即可,但 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)持有鎖了,同一個線程可以對同一個資源重復(fù)加鎖,代碼實現(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,在重入加鎖的情況下,需要重入解鎖相應(yīng)的次數(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)公平的關(guā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)用關(guān)系(只是簡單表明調(diào)用關(guān)系,并不是完整分支圖)如下:

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)用關(guān)系圖,下圖顯示的是無參 tryLock 的調(diào)用關(guān)系圖,如下:

我們需要注意的是 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 對應(yīng)的方法即可,不僅僅是 ReentrantLock 是這樣,JUC 中的其它鎖也都是這樣,只要對 AQS 了如指掌,鎖其實非常簡單。
以上就是java底層AQS實現(xiàn)類kReentrantLock鎖的構(gòu)成及源碼解析的詳細內(nèi)容,更多關(guān)于AQS實現(xiàn)類ReentrantLock鎖的構(gòu)成的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于java中@Async異步調(diào)用詳細解析附代碼
本文主要介紹了java關(guān)于@Async異步調(diào)用詳細解析附代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2022-07-07
實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)開發(fā)醫(yī)院科室及排班的接口
這篇文章主要為大家介紹了實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)開發(fā)醫(yī)院科室及排班的接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>2022-04-04
java保證對象在內(nèi)存中唯一性的實現(xiàn)方法
這篇文章主要介紹了java如何保證對象在內(nèi)存中的唯一性,如果創(chuàng)建多個對象的話,可能會引發(fā)出各種各樣的問題,這時,就需要我們保證這個對象在內(nèi)存中的唯一性,需要的朋友可以參考下2019-06-06
SpringCloud手寫Ribbon實現(xiàn)負載均衡
這篇文章主要介紹了SpringCloud手寫Ribbon實現(xiàn)負載均衡的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2021-01-01

