關(guān)于ReentrantLock的實(shí)現(xiàn)原理解讀
ReentrantLock 簡(jiǎn)介
ReentrantLock 實(shí)現(xiàn)了 Lock 接口,是一種可重入的獨(dú)占鎖。
相比于 synchronized 同步鎖,ReentrantLock 更加靈活,擁有更加強(qiáng)大的功能,比如可以實(shí)現(xiàn)公平鎖機(jī)制。
首先,先來(lái)了解一下什么是公平鎖機(jī)制。
ReentrantLock 的公平鎖機(jī)制
我們知道,ReentrantLock 分為公平鎖和非公平鎖,可以通過(guò)構(gòu)造方法來(lái)指定具體類(lèi)型:
//默認(rèn)非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}公平鎖
在多個(gè)線程競(jìng)爭(zhēng)獲取鎖時(shí),公平鎖傾向于將訪問(wèn)權(quán)授予等待時(shí)間最長(zhǎng)的線程。
也就是說(shuō),公平鎖相當(dāng)于有一個(gè)線程等待隊(duì)列,先進(jìn)入隊(duì)列的線程會(huì)先獲得鎖,按照 "FIFO(先進(jìn)先出)" 的原則,對(duì)于每一個(gè)等待線程都是公平的。
非公平鎖
非公平鎖是搶占模式,線程不會(huì)關(guān)注隊(duì)列中是否存在其他線程,也不會(huì)遵守先來(lái)后到的原則,直接嘗試獲取鎖。
接下來(lái)進(jìn)入正題,一起分析下 ReentrantLock 的底層是如何實(shí)現(xiàn)的。
ReentrantLock 的底層實(shí)現(xiàn)
ReentrantLock 實(shí)現(xiàn)的前提是 AbstractQueuedSynchronizer(抽象隊(duì)列同步器),簡(jiǎn)稱(chēng) AQS,是 java.util.concurrent 的核心,
常用的線程并發(fā)類(lèi) CountDownLatch、CyclicBarrier、Semaphore、ReentrantLock 等都包括了一個(gè)繼承自 AQS 抽象類(lèi)的內(nèi)部類(lèi)。
同步標(biāo)志位 state
AQS 內(nèi)部維護(hù)了一個(gè)同步標(biāo)志位 state,用來(lái)實(shí)現(xiàn)同步加鎖控制:
private volatile int state;
同步標(biāo)志位 state 的初始值為 0,線程每加一次鎖,state 就會(huì)加 1,也就是說(shuō),已經(jīng)獲得鎖的線程再次加鎖,state 值會(huì)再次加 1。
可以看出,state 實(shí)際上表示的是已獲得鎖的線程進(jìn)行加鎖操作的次數(shù)。
CLH 隊(duì)列
除了 state 同步標(biāo)志位外,AQS 內(nèi)部還使用一個(gè) FIFO 的隊(duì)列(也叫 CLH 隊(duì)列)來(lái)表示排隊(duì)等待鎖的線程,當(dāng)線程爭(zhēng)搶鎖失敗后會(huì)封裝成 Node 節(jié)點(diǎn)加入 CLH 隊(duì)列中去。

Node 的代碼實(shí)現(xiàn):
static final class Node {
// 標(biāo)識(shí)當(dāng)前節(jié)點(diǎn)在共享模式
static final Node SHARED = new Node();
// 標(biāo)識(shí)當(dāng)前節(jié)點(diǎn)在獨(dú)占模式
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
//前驅(qū)節(jié)點(diǎn)
volatile Node prev;
//后驅(qū)節(jié)點(diǎn)
volatile Node next;
//當(dāng)前線程
volatile Thread thread;
//存儲(chǔ)在condition隊(duì)列中的后繼節(jié)點(diǎn)
Node nextWaiter;
//是否為共享鎖
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}分析代碼可知, 每個(gè) Node 節(jié)點(diǎn)都有兩個(gè)指針,分別指向直接后繼節(jié)點(diǎn)和直接前驅(qū)節(jié)點(diǎn)。
Node 節(jié)點(diǎn)的變化過(guò)程
當(dāng)出現(xiàn)鎖競(jìng)爭(zhēng)以及釋放鎖的時(shí)候,AQS 同步隊(duì)列中的 Node 節(jié)點(diǎn)會(huì)發(fā)生變化,如下圖所示:
線程封裝成 Node 節(jié)點(diǎn)追加到隊(duì)列末尾,設(shè)置當(dāng)前節(jié)點(diǎn)的 prev 節(jié)點(diǎn)和 next 節(jié)點(diǎn)的指向;通過(guò) CAS 將 tail 重新指向新的尾部節(jié)點(diǎn),即當(dāng)前插入的 Node 節(jié)點(diǎn);
head 節(jié)點(diǎn)表示獲取鎖成功的節(jié)點(diǎn),當(dāng)頭結(jié)點(diǎn)釋放鎖后,會(huì)喚醒后繼節(jié)點(diǎn),如果后繼節(jié)點(diǎn)獲得鎖成功,就會(huì)把自己設(shè)置為頭結(jié)點(diǎn),節(jié)點(diǎn)的變化過(guò)程如下:

修改 head 節(jié)點(diǎn)指向下一個(gè)獲得鎖的節(jié)點(diǎn);新的獲得鎖的節(jié)點(diǎn),將 prev 的指針指向 null;
和設(shè)置 tail 的重新指向不同,設(shè)置 head 節(jié)點(diǎn)不需要用 CAS,是因?yàn)樵O(shè)置 head 節(jié)點(diǎn)是由獲得鎖的線程來(lái)完成的,而同步鎖只能由一個(gè)線程獲得,所以不需要 CAS 保證。
只需要把 head 節(jié)點(diǎn)設(shè)置為原首節(jié)點(diǎn)的后繼節(jié)點(diǎn),并且斷開(kāi)原 head 節(jié)點(diǎn)的 next 引用即可。
除了前驅(qū)和后繼節(jié)點(diǎn),Node 類(lèi)中還包括了 SHARED 和 EXCLUSIVE 節(jié)點(diǎn),它們起到了什么作用呢?這就不得不介紹一下 AQS 的兩種資源共享模式了。
AQS 的資源共享模式
AQS 通過(guò) EXCLUSIVE 和 SHARED 兩個(gè)變量來(lái)定義獨(dú)占模式或共享模式。
獨(dú)占模式
獨(dú)占模式是最常用的模式,使用范圍很廣,比如 ReentrantLock 的加鎖和釋放鎖就是使用獨(dú)占模式實(shí)現(xiàn)的。
獨(dú)占模式中的核心加鎖方法是 acquire():
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}這里首先調(diào)用 tryAcquire() 方法嘗試獲取鎖,也就是嘗試通過(guò) CAS 修改 state 為 1,如果發(fā)現(xiàn)鎖已經(jīng)被當(dāng)前線程占用,就執(zhí)行重入,也就是給 state+1;
如果鎖被其他線程占有,那么當(dāng)前線程執(zhí)行 tryAcquire 返回失敗,則會(huì)執(zhí)行 addWaiter() 方法在等待隊(duì)列中添加一個(gè)獨(dú)占式節(jié)點(diǎn),addWaiter() 方法實(shí)現(xiàn)如下:
private Node addWaiter(Node mode) {
//創(chuàng)建一個(gè)節(jié)點(diǎn),此處mode是獨(dú)占式的
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
// 如果tail節(jié)點(diǎn)非空,就將新節(jié)點(diǎn)的前節(jié)點(diǎn)設(shè)置為tail節(jié)點(diǎn),并將tail指向新節(jié)點(diǎn)
node.setPrevRelaxed(oldTail);
//CAS將tail更新為新節(jié)點(diǎn)
if (compareAndSetTail(oldTail, node)) {
//把原tail的next設(shè)為當(dāng)前節(jié)點(diǎn)
oldTail.next = node;
return node;
}
} else {
//還沒(méi)有初始化,就調(diào)用initializeSyncQueue()方法初始化
initializeSyncQueue();
}
}
}寫(xiě)入隊(duì)列后,需要掛起當(dāng)前線程,代碼如下:
/**
* 已經(jīng)入隊(duì)的線程嘗試獲取鎖
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; //標(biāo)記是否成功獲取鎖
try {
boolean interrupted = false; //標(biāo)記線程是否被中斷過(guò)
for (;;) {
final Node p = node.predecessor(); //獲取前驅(qū)節(jié)點(diǎn)
//如果前驅(qū)是head,即該結(jié)點(diǎn)是第二位,有資格去嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
setHead(node); // 獲取成功,將當(dāng)前節(jié)點(diǎn)設(shè)置為head節(jié)點(diǎn)
p.next = null; // 原h(huán)ead節(jié)點(diǎn)出隊(duì)
failed = false; //獲取成功
return interrupted; //返回是否被中斷過(guò)
}
// 判斷獲取失敗后是否可以掛起,若可以則掛起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 線程若被中斷,設(shè)置interrupted為true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}再看下 shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 都做了哪些事:
/**
* 判斷當(dāng)前線程獲取鎖失敗之后是否需要掛起.
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驅(qū)節(jié)點(diǎn)的狀態(tài)
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驅(qū)節(jié)點(diǎn)狀態(tài)為signal,返回true
return true;
// 前驅(qū)節(jié)點(diǎn)狀態(tài)為CANCELLED
if (ws > 0) {
// 從隊(duì)尾向前尋找第一個(gè)狀態(tài)不為CANCELLED的節(jié)點(diǎn)
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 將前驅(qū)節(jié)點(diǎn)的狀態(tài)設(shè)置為SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* 掛起當(dāng)前線程,返回線程中斷狀態(tài)并重置
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}通過(guò)以上代碼可以看出,線程入隊(duì)后能夠掛起的前提是,它的前驅(qū)節(jié)點(diǎn)的狀態(tài)為 SIGNAL,這意味著當(dāng)前一個(gè)節(jié)點(diǎn)獲取鎖并且出隊(duì)后,需要把后面的節(jié)點(diǎn)進(jìn)行喚醒。
加鎖說(shuō)完了再說(shuō)解鎖,解鎖的方法相比來(lái)說(shuō)更加簡(jiǎn)單,核心方法是 release():
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}代碼流程:先嘗試釋放鎖,若釋放成功,那么查看頭結(jié)點(diǎn)的狀態(tài)是否為 SIGNAL,如果是,則喚醒頭結(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)關(guān)聯(lián)的線程,如果釋放失敗就返回 false 表示解鎖失敗。
其中的 tryRelease() 方法實(shí)現(xiàn)如下,詳細(xì)流程見(jiàn)注釋說(shuō)明:
/**
* 釋放當(dāng)前線程占用的鎖
* @param releases
* @return 是否釋放成功
*/
protected final boolean tryRelease(int releases) {
// 計(jì)算釋放后state值
int c = getState() - releases;
// 如果不是當(dāng)前線程占用鎖,那么拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 鎖被重入次數(shù)為0,表示釋放成功
free = true;
// 清空獨(dú)占線程
setExclusiveOwnerThread(null);
}
// 更新state值
setState(c);
return free;
}共享模式
共享模式和獨(dú)占模式最大的區(qū)別在于,共享模式具有傳播的特性。
共享模式獲取鎖的方法為 acquireShared,相比于獨(dú)占模式,共享模式的加鎖多了一個(gè)步驟,即自己拿到資源后,還會(huì)去喚醒后繼隊(duì)友;
而共享模式釋放鎖的方法為 releaseShared,它會(huì)釋放指定量的資源,如果成功釋放且允許喚醒等待線程,會(huì)喚醒等待隊(duì)列里的其他線程來(lái)獲取資源。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
tio-boot整合hotswap-classloader實(shí)現(xiàn)熱加載方法實(shí)例
這篇文章主要為大家介紹了tio-boot整合hotswap-classloader實(shí)現(xiàn)熱加載方法實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Java設(shè)計(jì)模式之策略模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
策略模式是對(duì)算法的封裝,把一系列的算法分別封裝到對(duì)應(yīng)的類(lèi)中,并且這些類(lèi)實(shí)現(xiàn)相同的接口,相互之間可以替換。接下來(lái)通過(guò)本文給大家分享Java設(shè)計(jì)模式之策略模式,感興趣的朋友一起看看吧2017-08-08
Java編程實(shí)現(xiàn)深度優(yōu)先遍歷與連通分量代碼示例
這篇文章主要介紹了Java編程實(shí)現(xiàn)深度優(yōu)先遍歷與連通分量代碼示例,2017-11-11
Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁(yè)微信掃碼支付(完整版)
這篇文章主要介紹了Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁(yè)微信掃碼支付(完整版)的相關(guān)資料,非常不錯(cuò)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11
SpringBoot中的ExpiringMap代碼實(shí)例
這篇文章主要介紹了SpringBoot中的ExpiringMap代碼實(shí)例,ExpiringMap是一個(gè)可以設(shè)置過(guò)期策略、可變條目過(guò)期、延遲條目加載和過(guò)期偵聽(tīng)器的線程安全存儲(chǔ)容器,需要的朋友可以參考下2023-08-08
java實(shí)現(xiàn)excel和txt文件互轉(zhuǎn)
本篇文章主要介紹了java實(shí)現(xiàn)excel和txt文件互轉(zhuǎn)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
Java利用Zxing生成二維碼的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇Java利用Zxing生成二維碼的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08

