前言
JDK1.5 之后發(fā)布了JUC(java.util.concurrent),用于解決多線程并發(fā)問題。AQS 是一個特別重要的同步框架,很多同步類都借助于 AQS 實(shí)現(xiàn)了對線程同步狀態(tài)的管理。
AQS 中最主要的就是獨(dú)占鎖和共享鎖的獲取和釋放,以及提供了一些可中斷的獲取鎖,超時等待鎖等方法。
ReentranLock 是基于 AQS 獨(dú)占鎖的一個實(shí)現(xiàn)。ReentrantReadWriteLock 是基于 AQS 共享鎖的一個讀寫鎖實(shí)現(xiàn)。本來打算一篇文章里面寫完獨(dú)占鎖和共享鎖,但是發(fā)現(xiàn)篇幅太長了,也不易于消化。
因此,本篇就先結(jié)合 ReentrantLock 源碼,分析 AQS 的獨(dú)占鎖獲取和釋放。以及 ReentrantLock 的公平鎖和非公平鎖實(shí)現(xiàn)。
下一篇再寫 ReentrantReadWriteLock 讀寫鎖源碼,以及 AQS 共享鎖的獲取和釋放。
在正式講解源碼之前,墻裂建議讀者做一些準(zhǔn)備工作,最好對以下知識有一定的了解,這樣閱讀起來源碼會比較輕松(因?yàn)?,我?dāng)初剛開始接觸多線程時,直接看 AQS 簡直是一臉懵逼,就像讀天書一樣。。)。
- 了解雙向鏈表的數(shù)據(jù)結(jié)構(gòu),以及隊(duì)列的入隊(duì)出隊(duì)等操作。
- LockSupport 的 park,unpark 方法,以及對線程的 interrupt 幾個方法了解(可參考:LockSupport的 park 方法是怎么響應(yīng)中斷的?)。
- 對 CAS 和自旋機(jī)制有一定的了解。
AQS 同步隊(duì)列
AQS 內(nèi)部維護(hù)了一個 FIFO(先進(jìn)先出)的雙向隊(duì)列。它的內(nèi)部是用雙向鏈表來實(shí)現(xiàn)的,每個數(shù)據(jù)節(jié)點(diǎn)(Node)中都包含了當(dāng)前節(jié)點(diǎn)的線程信息,還有它的前后兩個指針,分別指向前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)。下邊看一下 Node 的屬性和方法:
static final class Node {
//可以認(rèn)為是一種標(biāo)記,表明了這個 node 是以共享模式在同步隊(duì)列中等待
static final Node SHARED = new Node();
//也是一種標(biāo)記,表明這個 node 是以獨(dú)占模式在同步隊(duì)列中等待
static final Node EXCLUSIVE = null;
/** waitStatus 常量值 */
//說明當(dāng)前節(jié)點(diǎn)被取消,原因有可能是超時,或者被中斷。
//節(jié)點(diǎn)被取消的狀態(tài)是不可逆的,也就是說此節(jié)點(diǎn)會一直停留在取消狀態(tài),不會轉(zhuǎn)變。
static final int CANCELLED = 1;
//說明后繼節(jié)點(diǎn)的線程被 park 阻塞,因此當(dāng)前線程需要在釋放鎖或者被取消時,喚醒后繼節(jié)點(diǎn)
static final int SIGNAL = -1;
//說明線程在 condition 條件隊(duì)列等待
static final int CONDITION = -2;
//在共享模式中用,表明下一個共享線程應(yīng)該無條件傳播
static final int PROPAGATE = -3;
//當(dāng)前線程的等待狀態(tài),除了以上四種值,還有一個值 0 為初始化狀態(tài)(條件隊(duì)列的節(jié)點(diǎn)除外)。
//注意這個值修改時是通過 CAS ,以保證線程安全。
volatile int waitStatus;
//前驅(qū)節(jié)點(diǎn)
volatile Node prev;
//后繼節(jié)點(diǎn)
volatile Node next;
//當(dāng)前節(jié)點(diǎn)中的線程,通過構(gòu)造函數(shù)初始化,出隊(duì)時會置空(這個后續(xù)說,重點(diǎn)強(qiáng)調(diào))
volatile Thread thread;
//有兩種情況。1.在 condition 條件隊(duì)列中的后一個節(jié)點(diǎn)
//2. 一個特殊值 SHARED 用于表明當(dāng)前是共享模式(因?yàn)闂l件隊(duì)列只存在于獨(dú)占模式)
Node nextWaiter;
//是否是共享模式,理由同上
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回前驅(qū)節(jié)點(diǎn),如果為空拋出空指針
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
另外,在 AQS 類中,還會記錄同步隊(duì)列的頭結(jié)點(diǎn)和尾結(jié)點(diǎn):
//同步隊(duì)列的頭結(jié)點(diǎn),是懶加載的,即不會立即創(chuàng)建一個同步隊(duì)列,
//只有當(dāng)某個線程獲取不到鎖,需要排隊(duì)的時候,才會初始化頭結(jié)點(diǎn)
private transient volatile Node head;
//同步隊(duì)列的尾結(jié)點(diǎn),同樣是懶加載。
private transient volatile Node tail;
獨(dú)占鎖
這部分就結(jié)合 ReentrantLock 源碼分析 AQS 的獨(dú)占鎖是怎樣獲得和釋放鎖的。
非公平鎖
首先,我們從 ReentrantLock 開始分析,它有兩個構(gòu)造方法,一個構(gòu)造,可以傳入一個 boolean 類型的參數(shù),表明是用公平鎖還是非公平鎖模式。另一個構(gòu)造方法,不傳入任何參數(shù),則默認(rèn)用非公平鎖。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
NonfairSync 和 FairSync 都繼承自 Sync ,它們都是 ReentranLock 的內(nèi)部類。 而Sync 類又繼承自 AQS (AbstractQueuedSynchronizer)。
static final class NonfairSync extends Sync {
}
static final class FairSync extends Sync {
}
abstract static class Sync extends AbstractQueuedSynchronizer {
}
知道了它們之間的繼承關(guān)系,我們就從非公平鎖的加鎖方法作為入口,跟蹤源碼。因?yàn)榉枪芥i的流程講明白之后,公平鎖大致流程都一樣,只是多了一個條件判斷(這個,一會兒后邊細(xì)講,會做對比)。
NonfairSync.lock
我們看下公平鎖的獲取鎖的方法:
final void lock() {
//通過 CAS 操作把 state 設(shè)置為 1
if (compareAndSetState(0, 1))
//如果設(shè)值成功,說明加鎖成功,保存當(dāng)前獲得鎖的線程
setExclusiveOwnerThread(Thread.currentThread());
else
//如果加鎖失敗,則執(zhí)行 AQS 的acquire 方法
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire
這個方法的邏輯是:
- 通過 tryAcquire 方法,嘗試獲取鎖,如果成功,則返回 true,失敗返回 false 。
- tryAcquire 失敗之后,會先調(diào)用 addWaiter 方法,把當(dāng)前線程封裝成 node 節(jié)點(diǎn),加入同步隊(duì)列(獨(dú)占模式)。
- acquireQueued 方法會把剛加入隊(duì)列的 node 作為參數(shù),通過自旋去獲得鎖。
tryAcquire
這是一個模板方法,具體的實(shí)現(xiàn)需要看它的子類,這里對應(yīng)的就是 ReentrantLock.NonfairSync.tryAcquire 方法。我們看一下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//當(dāng)前線程
final Thread current = Thread.currentThread();
//獲取當(dāng)前的同步狀態(tài),若為 0 ,表示無鎖狀態(tài)。若大于 0,表示已經(jīng)有線程搶到了鎖。
int c = getState();
if (c == 0) {
//然后通過 CAS 操作把 state 的值改為 1。
if (compareAndSetState(0, acquires)) {
// CAS 成功之后,保存當(dāng)前獲得鎖的線程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果 state 大于0,則判斷當(dāng)前線程是否是獲得鎖的線程,是的話,可重入。
else if (current == getExclusiveOwnerThread()) {
//由于 ReentrantLock 是可重入的,所以每重入一次 state 就加 1 。
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
addWaiter
如果獲取鎖失敗之后,就會調(diào)用 addWaiter 方法,把當(dāng)前線程加入同步隊(duì)列。
private Node addWaiter(Node mode) {
//把當(dāng)前線程封裝成 Node ,并且是獨(dú)占模式
Node node = new Node(Thread.currentThread(), mode);
//嘗試快速入隊(duì),如果失敗,則會調(diào)用 enq 入隊(duì)方法。enq 會初始化隊(duì)列。
Node pred = tail;
//如果 tail 不為空,說明當(dāng)前隊(duì)列中已經(jīng)有節(jié)點(diǎn)
if (pred != null) {
//把當(dāng)前 node 的 prev 指針指向 tail
node.prev = pred;
//通過 CAS 把 node 設(shè)置為 tail,即添加到隊(duì)尾
if (compareAndSetTail(pred, node)) {
//把舊的 tail 節(jié)點(diǎn)的 next 指針指向當(dāng)前 node
pred.next = node;
return node;
}
}
//當(dāng) tail 為空時,把 node 添加到隊(duì)列,如果需要的話,先進(jìn)行隊(duì)列初始化
enq(node);
//入隊(duì)成功之后,返回當(dāng)前 node
return node;
}
enq
通過自旋,把當(dāng)前節(jié)點(diǎn)加入到隊(duì)列中
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果 tail為空,說明隊(duì)列未初始化
if (t == null) {
//創(chuàng)建一個空節(jié)點(diǎn),通過 CAS把它設(shè)置為頭結(jié)點(diǎn)
if (compareAndSetHead(new Node()))
//此時只有一個 head頭節(jié)點(diǎn),因此把 tail也指向它
tail = head;
} else {
//第二次自旋時,tail不為空,于是把當(dāng)前節(jié)點(diǎn)的 prev指向 tail節(jié)點(diǎn)
node.prev = t;
//通過 CAS把 tail節(jié)點(diǎn)設(shè)置為當(dāng)前 node節(jié)點(diǎn)
if (compareAndSetTail(t, node)) {
//把舊的 tail節(jié)點(diǎn)的 next指向當(dāng)前 node
t.next = node;
return t;
}
}
}
}
acquireQueued
入隊(duì)成功之后,就會調(diào)用 acquireQueued 方法自旋搶鎖。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
final Node p = node.predecessor();
//如果前驅(qū)節(jié)點(diǎn)就是 head 節(jié)點(diǎn),就調(diào)用 tryAcquire 方法搶鎖
if (p == head && tryAcquire(arg)) {
//如果搶鎖成功,就把當(dāng)前 node 設(shè)置為頭結(jié)點(diǎn)
setHead(node);
p.next = null; // help GC
failed = false;
//搶鎖成功后,會把線程中斷標(biāo)志返回出去,終止for循環(huán)
return interrupted;
}
//如果搶鎖失敗,就根據(jù)前驅(qū)節(jié)點(diǎn)的 waitStatus 狀態(tài)判斷是否需要把當(dāng)前線程掛起
if (shouldParkAfterFailedAcquire(p, node) &&
//線程被掛起時,判斷是否被中斷過
parkAndCheckInterrupt())
//注意此處,如果被線程被中斷過,需要把中斷標(biāo)志重新設(shè)置一下
interrupted = true;
}
} finally {
if (failed)
//如果拋出異常,則取消鎖的獲取,進(jìn)行出隊(duì)操作
cancelAcquire(node);
}
}
setHead
通過代碼,我們可以看到,當(dāng)前的同步隊(duì)列中,只有第二個節(jié)點(diǎn)才有資格搶鎖。如果搶鎖成功,則會把它設(shè)置為頭結(jié)點(diǎn)。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
需要注意的是,這個方法,會把頭結(jié)點(diǎn)的線程設(shè)置為 null 。想一下,為什么?
因?yàn)?,此時頭結(jié)點(diǎn)的線程已經(jīng)搶鎖成功,需要出隊(duì)了。自然的,隊(duì)列中也就不應(yīng)該存在這個線程了。
PS:由 enq 方法,還有 setHead 方法,我們可以發(fā)現(xiàn),頭結(jié)點(diǎn)的線程總是為 null。這是因?yàn)椋^結(jié)點(diǎn)要么是剛初始化的空節(jié)點(diǎn),要么是搶到鎖的線程出隊(duì)了。因此,我們也常常把頭結(jié)點(diǎn)叫做虛擬節(jié)點(diǎn)(不存儲任何線程)。
shouldParkAfterFailedAcquire
以上是搶鎖成功的情況,那么搶鎖失敗了呢?這時,我們需要判斷是否應(yīng)該把當(dāng)前線程掛起。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的 waitStatus
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果 ws = -1 ,說明當(dāng)前線程可以被前驅(qū)節(jié)點(diǎn)正常喚醒,于是就可以安全的 park了
return true;
if (ws > 0) {
//如果 ws > 0,說明前驅(qū)節(jié)點(diǎn)被取消,則會從當(dāng)前節(jié)點(diǎn)依次向前查找,
//直到找到第一個沒有被取消的節(jié)點(diǎn),把那個節(jié)點(diǎn)的 next 指向當(dāng)前 node
//這一步,是為了找到一個可以把當(dāng)前線程喚起的前驅(qū)節(jié)點(diǎn)
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果 ws 為 0,或者 -3(共享鎖狀態(tài)),則把它設(shè)置為 -1
//返回 false,下次自旋時,就會判斷等于 -1,返回 true了
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt
如果 shouldParkAfterFailedAcquire 返回 true,說明當(dāng)前線程需要被掛起。因此,就執(zhí)行此方法,同時檢查線程是否被中斷。
private final boolean parkAndCheckInterrupt() {
//把當(dāng)前線程掛起,則 acquireQueued 方法的自旋就會暫停,等待前驅(qū)節(jié)點(diǎn) unpark
LockSupport.park(this);
//返回當(dāng)前節(jié)點(diǎn)是否被中斷的標(biāo)志,注意此方法會把線程的中斷標(biāo)志清除。
//因此,返回上一層方法時,需要設(shè)置 interrupted = true 把中斷標(biāo)志重新設(shè)置,以便上層代碼可以處理中斷
return Thread.interrupted();
}
想一下,為什么搶鎖失敗后,需要判斷是否把線程掛起?
因?yàn)?,如果搶不到鎖,并且還不把線程掛起,acquireQueued 方法就會一直自旋下去,這樣你的CPU能受得了嗎。
cancelAcquire
當(dāng)不停的自旋搶鎖時,若發(fā)生了異常,就會調(diào)用此方法,取消正在嘗試獲取鎖的線程。node 的位置分為三種情況,見下面注釋,
private void cancelAcquire(Node node) {
if (node == null)
return;
// node 不再指向任何線程
node.thread = null;
Node pred = node.prev;
//從當(dāng)前節(jié)點(diǎn)不斷的向前查找,直到找到一個有效的前驅(qū)節(jié)點(diǎn)
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
//把 node 的 ws 設(shè)置為 -1
node.waitStatus = Node.CANCELLED;
// 1.如果 node 是 tail,則把 tail 更新為 node,并把 pred.next 指向 null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//2.如果 node 既不是 tail,也不是 head 的后繼節(jié)點(diǎn),就把 node的前驅(qū)節(jié)點(diǎn)的 ws 設(shè)置為 -1
//最后把 node 的前驅(qū)節(jié)點(diǎn)的 next 指向 node 的后繼節(jié)點(diǎn)
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//3.如果 node是 head 的后繼節(jié)點(diǎn),則直接喚醒 node 的后繼節(jié)點(diǎn)。
//這個也很好理解,因?yàn)?node 是隊(duì)列中唯一有資格嘗試獲取鎖的節(jié)點(diǎn),
//它放棄了資格,當(dāng)然有義務(wù)把后繼節(jié)點(diǎn)喚醒,以讓后繼節(jié)點(diǎn)嘗試搶鎖。
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
unparkSuccessor
這個喚醒方法就比較簡單了,
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//從尾結(jié)點(diǎn)向前依次遍歷,直到找到距離當(dāng)前 node 最近的一個有效節(jié)點(diǎn)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//把這個有效節(jié)點(diǎn)的線程喚醒,
//喚醒之后,當(dāng)前線程就可以繼續(xù)自旋搶鎖了,(回到 park 的地方)
LockSupport.unpark(s.thread);
}
下面畫一個流程圖更直觀的查看整個獲取鎖的過程。

公平鎖
公平鎖和非公平鎖的整體流程大致相同,只是在搶鎖之前先判斷一下是否已經(jīng)有人排在前面,如果有的話,就不執(zhí)行搶鎖。我們通過源碼追蹤到 FairSync.tryAcquire 方法。會發(fā)現(xiàn),多了一個 hasQueuedPredecessors 方法。
hasQueuedPredecessors
這個方法判斷邏輯稍微有點(diǎn)復(fù)雜,有多種情況。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 如果 h == t,說明 h 和 t 都為空(此時隊(duì)列還未初始化)或者它們是同一個節(jié)點(diǎn)(說明隊(duì)列已經(jīng)初始化,并且只有一個節(jié)點(diǎn),此時為 enq 方法第一次自旋成功后)。此時,返回false。
- 如果 h != t,則判斷 head.next == null 是否成立,如果成立,則返回 true。這種情況發(fā)生在有其他線程第一次入隊(duì)時。在 AQS 的 enq 入隊(duì)方法,設(shè)置頭結(jié)點(diǎn)成功之后 compareAndSetHead(new Node()) ,還未執(zhí)行 tail = head 時(仔細(xì)想一想為什么?)。此時 tail = null , head = new Node(),head.next = null。
- 如果 h != t,并且 head.next != null,說明此時隊(duì)列中至少已經(jīng)有兩個節(jié)點(diǎn),則判斷 head.next 是否是當(dāng)前線程。如果是,返回 false(注意是 false哦,因?yàn)橛昧?!),否則返回 true 。
總結(jié):以上幾種情況,只有最終返回 false 時,才會繼續(xù)往下執(zhí)行。因?yàn)?false,說明沒有線程排在當(dāng)前線程前面,于是通過 CAS 嘗試把 state 值設(shè)置為 1。若成功,則方法返回。若失敗,同樣需要去排隊(duì)。
公平鎖和非公平鎖區(qū)別
舉個例子來對比公平鎖和非公平鎖。比如,現(xiàn)在到飯點(diǎn)了,大家都到食堂打飯。把隊(duì)列中的節(jié)點(diǎn)比作排隊(duì)打飯的人,每個打飯窗口都有一個管理員,只有排隊(duì)的人從管理員手中搶到鎖,才有資格打飯。打飯的過程就是線程執(zhí)行的過程。

如果,你發(fā)現(xiàn)前面沒有人在排隊(duì),那么就可以直接從管理員手中拿到鎖,然后打飯。對于公平鎖來說,如果你前面有人在打飯,那么你就要排隊(duì)到他后面(圖中B),等他打完之后,把鎖還給管理員。那么,你就可以從管理員手中拿到鎖,然后打飯了。后面的人依次排隊(duì)。這就是FIFO先進(jìn)先出的隊(duì)列模型。
對于非公平鎖來說,如果你是圖中的 B,當(dāng) A 把鎖還給管理員后,有可能有另外一個 D 插隊(duì)過來直接把鎖搶走。那么,他就可以打飯,你只能繼續(xù)等待了。
所以,可以看出來。公平鎖是嚴(yán)格按照排隊(duì)的順序來的,先來后到嘛,你來的早,就可以早點(diǎn)獲取鎖。優(yōu)點(diǎn)是,這樣不會造成某個線程等待時間過長,因?yàn)榇蠹叶际侵幸?guī)中矩的在排隊(duì)。而缺點(diǎn)呢,就是會頻繁的喚起線程,增加 CPU的開銷。
非公平鎖的優(yōu)點(diǎn)是吞吐量大,因?yàn)橛锌赡苷面i可用,然后線程來了,直接搶到鎖了,不用排隊(duì)了,這樣也減少了 CPU 喚醒排隊(duì)線程的開銷。 但是,缺點(diǎn)也很明顯,你說我排隊(duì)排了好長時間了,終于輪到我打飯了,憑什么其他人剛過來就插到我前面,比我還先打到飯,也太不公平了吧,后邊一大堆排隊(duì)的人更是怨聲載道。這要是每個人來了都插到我前面去,我豈不是要餓死了。
獨(dú)占鎖的釋放
我們從 ReentrantLock 的 unlock 方法看起:
public void unlock() {
//調(diào)用 AQS 的 release 方法
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//如果頭結(jié)點(diǎn)不為空,并且 ws 不為 0,則喚起后繼節(jié)點(diǎn)
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
這段邏輯比較簡單,當(dāng)線程釋放鎖之后,就會喚醒后繼節(jié)點(diǎn)。 unparkSuccessor 已講,不再贅述。然后看下 tryRelease 方法,公平鎖和非公平鎖走的是同一個方法。
protected final boolean tryRelease(int releases) {
//每釋放一次鎖,state 值就會減 1,因?yàn)橹翱赡苡墟i的重入
int c = getState() - releases;
//如果當(dāng)前線程不是搶到鎖的線程,則拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//只有 state 的值減到 0 的時候,才會全部釋放鎖
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
因?yàn)?,ReentrantLock 支持鎖的重入,所以每次重入 state 值都會加 1,相應(yīng)的每次釋放鎖, state 的值也會減 1 。所以,這也是為什么每個 lock 方法最后都要有一個 unlock 方法釋放鎖,它們的個數(shù)需要保證相同。
當(dāng) state 值為 0 的時候,說明鎖完全釋放。其他線程才可以有機(jī)會搶到鎖。
結(jié)語
以上已經(jīng)講解了獨(dú)占鎖主要的獲取方法 acquire ,另外還有一些其他相關(guān)方法,不再贅述,因?yàn)橹饕壿嫸际且粯拥模挥胁糠稚杂胁煌?,只要理解?acquire ,這些都是相通的。如 acquireInterruptibly 方法,它可以在獲取鎖的時候響應(yīng)中斷。還有超時獲取鎖的方法 doAcquireNanos 可以設(shè)定獲取鎖的超時時間,超時之后就返回失敗。
下篇預(yù)告:分析 ReentrantReadWriteLock 讀寫鎖源碼,以及 AQS 共享鎖的獲取和釋放,敬請期待。
如果本文對你有用,歡迎點(diǎn)贊,評論,轉(zhuǎn)發(fā)。
學(xué)習(xí)是枯燥的,也是有趣的。我是「煙雨星空」,歡迎關(guān)注,可第一時間接收文章推送。