顯示鎖ReentrantLock的內(nèi)部同步依賴于AQS(AbstractQueuedSynchronizer)伟阔,因此,分析ReentrantLock必然涉及AQS掰伸。
本文假設(shè)讀者已熟練掌握AQS的基本原理(參考AQS的基本原理)皱炉,通過分析ReentrantLock#lock()與ReentrantLock#unlock()的實(shí)現(xiàn)原理,用實(shí)例幫助讀者理解AQS的等待隊(duì)列模型狮鸭。
JDK版本:oracle java 1.8.0_102
接口聲明
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock對標(biāo)內(nèi)置鎖合搅,實(shí)現(xiàn)了Lock接口。忽略Condition相關(guān)歧蕉,主要提供lock灾部、unlock兩種語義,和兩種語義的衍生品惯退。
實(shí)現(xiàn)原理
繼承AQS
本文中的“繼承”指“擴(kuò)展extend”赌髓。
AQS復(fù)習(xí)
AQS并提供了多個(gè)未實(shí)現(xiàn)的protected方法,留給作者覆寫以開發(fā)不同的同步器:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
...
}
而其他非私有方法則使用final修飾催跪,禁止子類覆寫锁蠕。
繼承
ReentrantLock支持公平、非公平兩種策略懊蒸,并通過繼承AQS實(shí)現(xiàn)了對應(yīng)兩種策略的同步器NonfairSync與FairSync荣倾。ReentrantLock默認(rèn)使用非公平策略,即NonfairSync:
public ReentrantLock() {
sync = new NonfairSync();
}
...
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
...
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
...
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
...
}
protected final boolean tryRelease(int releases) {
...
}
final ConditionObject newCondition() {
return new ConditionObject();
}
...
}
...
先不追究細(xì)節(jié)榛鼎。下面以默認(rèn)的非公平策略為例逃呼,講解lock和unlock的實(shí)現(xiàn)。
lock
public void lock() {
sync.lock();
}
非公平策略下者娱,sync指向一個(gè)NonfairSync實(shí)例抡笼。
static final class NonfairSync extends Sync {
...
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
...
}
ReentrantLock用state表示“所有者線程已經(jīng)重復(fù)獲取該鎖的次數(shù)”。當(dāng)state等于0時(shí)黄鳍,表示當(dāng)前沒有線程持有該鎖推姻,因此,將state CAS設(shè)置為1框沟,并記錄排他的所有者線程ownerThread(ownerThread只會(huì)在0->1及1->0兩次狀態(tài)轉(zhuǎn)換中修改)藏古;否則,state必然大于0忍燥,則嘗試再獲取一次鎖拧晕。ownerThread將在state大于0時(shí),用于判斷重入性梅垄。
- 排他性:如果線程T1已經(jīng)持有鎖L厂捞,則不允許除T1外的任何線程T持有該鎖L
- 重入性:如果線程T1已經(jīng)持有鎖L,則允許線程T1多次獲取鎖L,更確切的說靡馁,獲取一次后欲鹏,可多次進(jìn)入鎖。
二者結(jié)合臭墨,描述了ReentrantLock的一個(gè)性質(zhì):允許ownerThread重入赔嚎,不允許其他線程進(jìn)入或重入。
acquire
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
...
}
改寫:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
public final void acquire(int arg) {
if (tryAcquire(arg)) {
return;
}
Node newNode = addWaiter(Node.EXCLUSIVE);
boolean interrupted = acquireQueued(newNode, arg);
if (interrupted) {
selfInterrupt();
}
}
...
}
首先胧弛,通過tryAcquire()嘗試獲取鎖尤误。按照AQS的約定,tryAcquire()返回true表示獲取成功叶圃,可直接返回袄膏;否則獲取失敗
掺冠。如果獲取失敗,則向等待隊(duì)列中添加一個(gè)獨(dú)占模式的節(jié)點(diǎn)码党,并通過acquireQueued()阻塞的等待該節(jié)點(diǎn)被調(diào)用(即當(dāng)前線程被喚醒)德崭。如果是因?yàn)楸恢袛喽鴨拘训模瑒t復(fù)現(xiàn)中斷信號(hào)揖盘。
tryAcquire
NonfairSync覆寫了AQS#tryAcquire():
static final class NonfairSync extends Sync {
...
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
...
}
abstract static class Sync extends AbstractQueuedSynchronizer {
...
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
...
}
12-17行重復(fù)了NonfairSync#lock()中state=0時(shí)的狀態(tài)轉(zhuǎn)換眉厨。18行進(jìn)行排他性判斷,如果當(dāng)前線程等于ownerThread兽狭,則直接返回false憾股。否則,19-22行進(jìn)行重入箕慧,state加1(acquires=1)服球,表示所有者線程重復(fù)獲取該鎖的次數(shù)增加1。
實(shí)際上颠焦,NonfairSync#lock()不需要特殊處理state=0時(shí)的狀態(tài)轉(zhuǎn)換斩熊。可通過NonfairSync#tryAcquire()伐庭、Sync#nonfairTryAcquire()完成粉渠。
為什么19-22行不需要同步
注意,如果18行判斷當(dāng)前線程等于ownerThread圾另,則根據(jù)程序順序規(guī)則霸株,19-22行不需要同步。因?yàn)?strong>同一線程中集乔,第二次調(diào)用NonfairSync#tryAcquire()時(shí)(會(huì)進(jìn)入19-22行)去件,第一次調(diào)用鎖寫入的state、ownerThread一定是可見的。
為什么要用state表示重入次數(shù)
如果沒有記錄重入次數(shù)箫攀,則第一次釋放鎖時(shí)肠牲,會(huì)一次性把ownerThread多次重入的鎖都釋放掉,而此時(shí)“鎖中的代碼”還沒有執(zhí)行完成靴跛,造成混亂缀雳。
addWaiter
如果tryAcquire()獲取失敗,則要通過AQS#addWaiter()向等待隊(duì)列中添加一個(gè)獨(dú)占模式的節(jié)點(diǎn)梢睛,并返回該節(jié)點(diǎn):
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
...
}
AQS#enq()中重復(fù)了9-15行的邏輯肥印,直接看enq()。
如果尾指針為null绝葡,則頭指針也一定為null深碱,表示等待隊(duì)列未初始化,就CAS初始化隊(duì)列(常見于無阻塞隊(duì)列的設(shè)計(jì)中藏畅,如源碼|并發(fā)一枝花之BlockingQueue)敷硅,然后繼續(xù)循環(huán)。如果尾指針非null愉阎,則隊(duì)列已初始化绞蹦,就CAS嘗試在尾節(jié)點(diǎn)后插入新的節(jié)點(diǎn)node。
在插入過程中榜旦,會(huì)出現(xiàn)“node.prev指向舊的尾節(jié)點(diǎn)幽七,但舊的尾節(jié)點(diǎn).next為null未指向node(盡管,尾指針指向node)”的狀態(tài)溅呢,即“隊(duì)列在prev方向一致澡屡,next方向不一致”。記住該狀態(tài)咐旧,分析ReentrantLock#unlock()時(shí)會(huì)用到驶鹉。
最后,enq()返回舊的尾節(jié)點(diǎn)休偶。但外層的AQS#addWaiter()仍然返回新節(jié)點(diǎn)node梁厉。
隊(duì)列剛完成初始化時(shí),存在一個(gè)dummy node踏兜。插入節(jié)點(diǎn)時(shí)词顾,tail后移指向新節(jié)點(diǎn),head不變?nèi)匀恢赶騞ummy node碱妆。直到調(diào)用AQS#acquireQueued()時(shí)肉盹,head才會(huì)后移,消除了dummy node疹尾,后面分析上忍。
acquireQueued
插入新節(jié)點(diǎn)node后骤肛,通過AQS#acquireQueued()阻塞的等待該節(jié)點(diǎn)被調(diào)用(即當(dāng)前線程被喚醒):
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
...
}
該方法是lock過程的核心難點(diǎn),需要結(jié)合AQS#addWaiter()理解AQS內(nèi)部基于等待隊(duì)列的同步模型窍蓝。
AQS的核心是狀態(tài)依賴腋颠,可概括為兩條規(guī)則:
- 當(dāng)狀態(tài)還沒有滿足的時(shí)候,節(jié)點(diǎn)會(huì)進(jìn)入等待隊(duì)列吓笙。
- 特別的淑玫,獲取成功的節(jié)點(diǎn)成為隊(duì)列的頭結(jié)點(diǎn)。
首先面睛,AQS#addWaiter()會(huì)將新節(jié)點(diǎn)node加入隊(duì)尾(維護(hù)規(guī)則“當(dāng)狀態(tài)還沒有滿足的時(shí)候絮蒿,節(jié)點(diǎn)會(huì)進(jìn)入等待隊(duì)列”),然后叁鉴,AQS#acquireQueued()檢查node的前繼節(jié)點(diǎn)是否是頭節(jié)點(diǎn)土涝。如果是,則嘗試獲取鎖幌墓;如果不是但壮,或獲取所失敗,都會(huì)嘗試阻塞等待克锣。
如果11行獲取鎖成功茵肃,則更新頭節(jié)點(diǎn)(維護(hù)規(guī)則“獲取成功的節(jié)點(diǎn)成為隊(duì)列的頭結(jié)點(diǎn)”),修改failed標(biāo)志袭祟,并返回interrupted標(biāo)志。interrupted初始化為false捞附,可能在17-19行被修改巾乳。
初始化隊(duì)列后的第一次更新頭結(jié)點(diǎn),直接setHead消除了dummy node鸟召。消除之后胆绊,實(shí)際節(jié)點(diǎn)代替了dummy node的作用,但與dummy node不同的是欧募,該節(jié)點(diǎn)是持有鎖的压状。
如果11行判斷前繼節(jié)點(diǎn)不是頭節(jié)點(diǎn)或獲取鎖失敗,則進(jìn)入17-19行跟继。AQS.shouldParkAfterFailedAcquire()判斷是否需要阻塞等待种冬,如果需要,則通過AQS#parkAndCheckInterrupt()阻塞等待舔糖,直到被喚醒或被中斷娱两。
shouldParkAfterFailedAcquire
AQS.shouldParkAfterFailedAcquire()根據(jù)pred.waitStatus判斷新節(jié)點(diǎn)node是否應(yīng)該被阻塞:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
...
}
AQS#addWaiter()構(gòu)造新節(jié)點(diǎn)時(shí),pred.waitStatus使用了默認(rèn)值0金吗。此時(shí)十兢,進(jìn)入14-16行趣竣,CAS設(shè)置pred.waitStatus為SIGNAL==-1
。最后返回false旱物。
回到AQS#acquireQueued()中后遥缕,由于AQS#parkAndCheckInterrupt()返回false,循環(huán)會(huì)繼續(xù)進(jìn)行宵呛。假設(shè)node的前繼節(jié)點(diǎn)pred仍然不是頭結(jié)點(diǎn)或鎖獲取失敗单匣,則會(huì)再次進(jìn)入AQS#parkAndCheckInterrupt()。上一輪循環(huán)中烤蜕,已經(jīng)將pred.waitStatus設(shè)置為SIGNAL==-1
警绩,則這次會(huì)進(jìn)入7-8行,直接返回true嚣州,表示應(yīng)該阻塞归粉。
什么時(shí)候會(huì)遇到ws > 0
的case呢?當(dāng)pred所維護(hù)的獲取請求被取消時(shí)橱鹏,pred.waitStatus會(huì)被設(shè)置為CANCELLED==1
膜蠢,從而進(jìn)入9-14行。改寫:
if (ws > 0) {
do {
pred = pred.prev;
node.prev = pred;
} while (pred.waitStatus > 0);
pred.next = node;
}
邏輯很簡單莉兰,循環(huán)移除所有被取消的前繼節(jié)點(diǎn)pred挑围,直到找到未被取消的pred。移除所有被取消的前繼節(jié)點(diǎn)后糖荒,直接返回false杉辙。
注意,在執(zhí)行6行之前捶朵,隊(duì)列處于“node.prev指向最新的前繼節(jié)點(diǎn)蜘矢,但pred.next指向已經(jīng)移除的后繼節(jié)點(diǎn)”的狀態(tài),即“隊(duì)列在prev方向一致综看,next方向不一致”品腹。記住該狀態(tài),分析ReentrantLock#unlock()時(shí)會(huì)用到红碑。
此處不需要檢查前繼節(jié)點(diǎn)是否為null舞吭。因?yàn)榈却?duì)列的頭結(jié)點(diǎn)要么是dummy node,滿足dummy.waitStatus==0
析珊;要么是剛替換的real node羡鸥,滿足real.waitStatus==0
;要么是后繼節(jié)點(diǎn)已經(jīng)阻塞的節(jié)點(diǎn)唾琼,滿足real.waitStatus==SIGNAL==-1
兄春。則最晚遍歷到頭結(jié)點(diǎn)時(shí),一定會(huì)退出循環(huán)锡溯,不會(huì)出現(xiàn)pred為null的情況赶舆。
回到AQS#acquireQueued()后哑姚,重新檢查前繼節(jié)點(diǎn)是否為頭節(jié)點(diǎn),并作出相應(yīng)處理芜茵。
經(jīng)過多次循環(huán)執(zhí)行AQS.shouldParkAfterFailedAcquire()后叙量,等待隊(duì)列趨于穩(wěn)定。最終的穩(wěn)定狀態(tài)為:
- 除了頭節(jié)點(diǎn)九串,剩余節(jié)點(diǎn)都會(huì)返回true绞佩,表示需要阻塞等待。
-
除了尾節(jié)點(diǎn)猪钮,剩余節(jié)點(diǎn)都滿足
waitStatus==SIGNAL
品山,表示釋放后需要喚醒后繼節(jié)點(diǎn)。
parkAndCheckInterrupt
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
...
}
AQS#parkAndCheckInterrupt()借助LockSupport.park()實(shí)現(xiàn)阻塞等待烤低。最后調(diào)用Thread.interrupted()檢查是否被中斷肘交,并清除中斷狀態(tài),并返回中斷標(biāo)志扑馁。
如果是被中斷的涯呻,則需要在外層AQS#acquireQueued()中重新設(shè)置中斷標(biāo)志interrupted,并在下一次循環(huán)中返回腻要。然后在更外層的AQS#acquire()中調(diào)用AQS.selfInterrupt()重放中斷复罐。
為什么不能直接在AQS#parkAndCheckInterrupt()返回后中斷?因?yàn)榉祷刂修D(zhuǎn)標(biāo)志能提供更大的靈活性雄家,外界可以自行決定是即時(shí)重放效诅、稍后重放還是壓根不重放。Condition在得知AQS#acquireQueued()是被中斷的之后趟济,便沒有直接復(fù)現(xiàn)中斷填帽,而是根據(jù)
REINTERRUPT
配置決定是否重放。
cancelAcquire
最后咙好,如果在執(zhí)行AQS#acquire()的過程中拋出任何異常,則取消任務(wù):
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
private void cancelAcquire(Node node) {
...
node.waitStatus = Node.CANCELLED;
...
}
...
}
因此褐荷,如果指考慮ReentrantLock#lock()方法的話勾效,那么被標(biāo)記為CACELLED狀態(tài)的節(jié)點(diǎn)一定在獲取鎖時(shí)拋出了異常,AQS.shouldParkAfterFailedAcquire()中清理了這部分CACELLED節(jié)點(diǎn)叛甫。
超時(shí)版ReentrantLock#tryLock()中层宫,還可以由于超時(shí)觸發(fā)取消。
lock小結(jié)
ReentrantLock#lock()收斂后其监,AQS內(nèi)部的等待隊(duì)列如圖:
unlock
ReentrantLock#unlcok()與ReentrantLock#lcok()是對偶的萌腿。
public void unlock() {
sync.release(1);
}
獲取鎖以單位1進(jìn)行,釋放鎖時(shí)也以單位1進(jìn)行抖苦。
release
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
...
}
首先毁菱,通過tryRelease()嘗試釋放鎖米死。按照AQS的約定,tryRelease()返回true表示已完全釋放贮庞,可喚醒所有阻塞線程峦筒;否則沒有完全釋放,不需要喚醒窗慎。如果已完全釋放物喷,則只需要喚醒頭結(jié)點(diǎn)的后繼節(jié)點(diǎn),該節(jié)點(diǎn)的ownerThread必然與頭結(jié)點(diǎn)不同(如果相同遮斥,則之前l(fā)ock時(shí)能夠重入峦失,不需要排隊(duì));否則沒有完全釋放术吗,不需要喚醒任何節(jié)點(diǎn)尉辑。
對于獨(dú)占鎖,“完全釋放”表示ownerThread的所有重入操作均已結(jié)束藐翎。
解釋8行的判斷
如果h == null
材蹬,則隊(duì)列還未初始化(回憶AQS#enq())。如果h.waitStatus == 0
吝镣,則要么剛剛初始化隊(duì)列堤器,只有一個(gè)dummy node,沒有后繼節(jié)點(diǎn)(回憶AQS#enq())末贾;要么后繼節(jié)點(diǎn)還沒被阻塞闸溃,不需要喚醒(回憶等待隊(duì)列的穩(wěn)定狀態(tài))。
tryRelease
對照tryAcquire()分析tryRelease():
abstract static class Sync extends AbstractQueuedSynchronizer {
...
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
...
}
5-6行很重要拱撵。如果存在某線程持有鎖辉川,則可以檢查unlock是否被ownerThread觸發(fā);如果不存在線程持有鎖拴测,則ownerThread==null
乓旗,可以檢查是否在未lock的情況下進(jìn)行unlock,或者重復(fù)執(zhí)行了unlock集索。
因此屿愚,使用ReentrantLock時(shí),try-finally要這么寫:
Lock lock = new ReentrantLock(); lock.lock(); try { // do sth } finally { lock.unlcok(); }
確保在調(diào)用lock()成功之后务荆,才能調(diào)用unlock()妆距。
接下來,8行判斷是否將要進(jìn)行1->0
的狀態(tài)轉(zhuǎn)換函匕,如果是娱据,則可以完全釋放鎖,將ownerThread置為null盅惜。然后設(shè)置state中剩。
最后忌穿,返回是否可完全釋放的標(biāo)志free。
可見性問題
為了抓住核心功能咽安,前面一直忽略了一個(gè)很重要的問題——可見性
伴网。忽略可見性問題的話,閱讀源碼基本沒有影響妆棒,但自己實(shí)現(xiàn)同步器時(shí)將帶來噩夢澡腾。
以此處為例,是應(yīng)該先執(zhí)行8-11行糕珊,還是先執(zhí)行12行呢动分?或者是無所謂呢?
為保障可見性红选,必須先執(zhí)行8-11行澜公,再執(zhí)行12行。因?yàn)?strong>exclusiveOwnerThread的可見性要借助于(piggyback)于volatile變量state:
...
private transient Thread exclusiveOwnerThread;
...
private volatile int state;
...
配套的喇肋,也必須先讀state坟乾,再讀exclusiveOwnerThread:
abstract static class Sync extends AbstractQueuedSynchronizer {
...
final boolean nonfairTryAcquire(int acquires) {
...
int c = getState(); // 先讀state
if (c == 0) {
...
}
else if (current == getExclusiveOwnerThread()) { // 再讀exclusiveOwnerThread
...
}
return false;
}
...
}
核心是三條Happens-Before規(guī)則:
-
程序順序規(guī)則
:如果程序中操作A在操作B之前,那么在線程中操作A將在操作B之前執(zhí)行蝶防。 -
傳遞性
:如果操作A在操作B之前執(zhí)行甚侣,并且操作B在操作C之前執(zhí)行,那么操作A必須在操作C之前執(zhí)行间学。 -
volatile變量規(guī)則
:對volatile變量的寫入操作必須在對該變量的讀操作之前執(zhí)行殷费。
具體來說,“先寫exclusiveOwnerThread再寫state低葫;先讀state再讀exclusiveOwnerThread”的方案详羡,保證了“在讀state之后,發(fā)生在寫state之前的寫exclusiveOwnerThread操作對發(fā)生在讀state之后的讀exclusiveOwnerThread操作一定是可見的”嘿悬。
程序順序規(guī)則实柠、傳遞性兩條基本規(guī)則,經(jīng)常與監(jiān)視器鎖規(guī)則善涨、volatile變量規(guī)則顯示的搭配主到,一定要掌握。
相對的躯概,線程啟動(dòng)規(guī)則、線程結(jié)束規(guī)則畔师、中斷規(guī)則娶靡、終結(jié)器規(guī)則則通常被隱式的使用。
unparkSuccessor
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
...
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;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
...
}
對lock過程的分析中看锉,我們得知姿锭,隊(duì)列中所有節(jié)點(diǎn)的waitStatus要么為0塔鳍,要么為SIGNAL==-1
。當(dāng)node.waitStatus==SIGNAL
時(shí)呻此,表示node的后繼節(jié)點(diǎn)s已被阻塞或正在被阻塞÷秩遥現(xiàn)在需要喚醒s,則7-8行將node.waitStatus置0焚鲜。
注意掌唾,node.waitStatus一定不為
CANCELLED==1
,因?yàn)槿绻鹟ock()方法沒有執(zhí)行成功忿磅,就無法通過unlock()方法調(diào)用AQS#unparkSuccessor()糯彬。
接下來,10-18行從尾節(jié)點(diǎn)向前遍歷葱她,找到node后最靠近node的未取消的節(jié)點(diǎn)撩扒,如果存在該節(jié)點(diǎn)s(s!=null
),就喚醒s.thread以競爭鎖吨些。
一致性問題
為什么要從尾節(jié)點(diǎn)向前遍歷搓谆,而不能從node向后遍歷?這是因?yàn)楹朗珹QS中的等待隊(duì)列基于一個(gè)弱一致性雙向鏈表實(shí)現(xiàn)泉手,允許某些時(shí)刻下,隊(duì)列在prev方向一致但校,next方向不一致螃诅。
理想情況下,隊(duì)列每時(shí)每刻都處于一致的狀態(tài)(強(qiáng)一致性模型)状囱,從node向后遍歷找第一個(gè)未取消節(jié)點(diǎn)是更高效的做法术裸。然而,維護(hù)一致性通常需要犧牲部分性能亭枷,為了進(jìn)一步的提升性能袭艺,腦洞大開的神牛們想出了各種高性能的弱一致性模型。盡管模型允許了更多弱一致狀態(tài)叨粘,但所有弱一致狀態(tài)都在控制之下猾编,不會(huì)出現(xiàn)一致性問題。
回憶lock過程的分析升敲,有兩個(gè)地方出現(xiàn)了這個(gè)弱一致狀態(tài):
- AQS#enq()插入新節(jié)點(diǎn)(包括AQS#addWaiter())的過程中答倡,舊的尾節(jié)點(diǎn)next為null未指向新節(jié)點(diǎn)。對應(yīng)條件
s == null
驴党。如圖:
- AQS.shouldParkAfterFailedAcquire()移除CACELLED節(jié)點(diǎn)的過程中瘪撇,中間節(jié)點(diǎn)指向已被移除的CACELLED節(jié)點(diǎn)。對應(yīng)條件
s.waitStatus > 0
。如圖:
因此倔既,從node開始恕曲,沿著next方向向后遍歷是行不通的。只能從tail開始渤涌,沿著prev方向向前遍歷佩谣,直到找到未取消的節(jié)點(diǎn)(s != null
),或遍歷完node的所有后繼子孫(s == null
)实蓬。
當(dāng)然茸俭,
s == null
也可能表示node恰好是尾節(jié)點(diǎn),該狀態(tài)是強(qiáng)一致的瞳秽,但仍然可以復(fù)用該段代碼瓣履。
unlock小結(jié)
ReentrantLock#unlock()收斂后,AQS內(nèi)部的等待隊(duì)列如圖:
總結(jié)
本文的目標(biāo)是借助對ReentrantLock#lock()與ReentrantLock#unlock()的分析练俐,理解AQS的等待隊(duì)列模型袖迎。
實(shí)際上,ReentrantLock還有一些重要的特性和API腺晾,如ReentrantLock#lockInterruptibly()燕锥、ReentrantLock#newCondition()。后面分先后分析這兩個(gè)API悯蝉,加深對AQS的理解归形。
本文鏈接:源碼|并發(fā)一枝花之ReentrantLock與AQS(1):lock、unlock
作者:猴子007
出處:https://monkeysayhi.github.io
本文基于 知識(shí)共享署名-相同方式共享 4.0 國際許可協(xié)議發(fā)布鼻由,歡迎轉(zhuǎn)載暇榴,演繹或用于商業(yè)目的,但是必須保留本文的署名及鏈接蕉世。