基於AQS的ReentrantLock 原理(多圖多源碼預警)
在上一篇博客中,我使用AQS實現了一把自定義鎖,這樣可以使我們更好的理解基於AQS的鎖體系
也可以更好得理解本文中的一些關鍵詞
非公平鎖實現原理
加鎖解鎖流程
先從構造器開始看,默認爲非公平鎖實現
public ReentrantLock() {
sync = new NonfairSync();
}
NonfairSync
繼承自 AQS(同步器),它長這個樣子
在本文第一段引用的文章末尾,我介紹了AQS的阻塞隊列相關,裏面對這把同步器裏的幾個元素作了解釋,各位可自行查看
在有一個線程持有這把鎖時,它長這個樣子:
第一個競爭出現時:
public void lock() {
sync.lock();
}
**************************************************************************
final void lock() {
// 首先用 cas 嘗試(僅嘗試一次)將 state 從 0 改爲 1, 如果成功表示獲得了獨佔鎖
if (compareAndSetState(0, 1)) //0——>1
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果嘗試失敗,進入阻塞隊列等待喚醒
acquire(1);
}
**************************************************************************
// ㈠ AQS 繼承過來的方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 當 tryAcquire 返回爲 false 時, 先調用 addWaiter , 接着 acquireQueued
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
**************************************************************************
線程2執行了:
-
CAS 嘗試將 state 由 0 改爲 1,結果失敗
-
進入 tryAcquire 邏輯,這時 state 已經是1,結果仍然失敗
-
接下來進入 addWaiter 邏輯,構造 Node 隊列
private Node addWaiter(Node mode) {
// 將當前線程關聯到一個 Node 對象上, 模式爲獨佔模式
Node node = new Node(Thread.currentThread(), mode);
// 如果 tail 不爲 null, cas 嘗試將 Node 對象加入 AQS 隊列尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// 雙向鏈表
pred.next = node;
return node;
}
}
// 嘗試將 Node 加入 AQS
enq(node);
return node;
}
**************************************************************************
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 還沒有, 設置 head 爲哨兵節點(不對應線程,狀態爲 0)
if (compareAndSetHead(new Node())) {
tail = head;
}
} else {
// cas 嘗試將 Node 對象加入 AQS 隊列尾部
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}} }}
完成後,形成了這樣一個數據結構,曰之雙端ELC阻塞隊列
-
圖中黃色三角表示該 Node 的 waitStatus 狀態,其中 0 爲默認正常狀態
-
Node 的創建是懶惰的
-
其中第一個 Node 稱爲 Dummy(啞元)或哨兵,用來佔位,並不關聯線程,主要作用是喚醒與之關聯的線程
當前線程進入 acquireQueued 邏輯
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循環
for (;;) {
//獲得當前線程前驅節點:head
final Node p = node.predecessor();
// 上一個節點是 head, 表示輪到自己(當前線程對應的 node)了, 嘗試獲取
if (p == head && tryAcquire(arg)) {
// 獲取成功, 設置自己(當前線程對應的 node)爲 head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//鎖被另一個線程佔有,失敗
if (shouldParkAfterFailedAcquire(p, node) &&
//park並檢查是否被打斷,是則設置打斷標記, 此時 Node 的狀態被置爲 Node.SIGNAL
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
//放棄申請鎖
cancelAcquire(node);
}
}
**************************************************************************
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取上一個節點的狀態
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) {
// 上一個節點都在阻塞, 那麼自己也阻塞好了
return true;
}
// > 0 表示取消狀態
if (ws > 0) {
// 上一個節點取消, 那麼重構刪除前面所有取消的節點, 返回到外層循環重試
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 這次還沒有阻塞
// 但下次如果重試不成功, 則需要阻塞,這時需要設置上一個節點狀態爲 Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
**************************************************************************
// 阻塞當前線程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
}
-
acquireQueued
會在一個死循環中不斷嘗試獲得鎖,失敗後進入 park 阻塞 -
如果自己是緊鄰着 head(排第二位),那麼再次
tryAcquire
嘗試獲取鎖,當然這時 state 仍爲 1,失敗 -
進入
shouldParkAfterFailedAcquire
邏輯,將前驅 node,即 head 的 waitStatus 改爲 -1,這次返回 false
當再次有多個線程經歷上述過程競爭失敗,變成這個樣子:
線程1釋放鎖,進入 tryRelease 流程,如果成功
-
設置 exclusiveOwnerThread 爲 null
-
state = 0
public final boolean release(int arg) {
// 嘗試釋放鎖
if (tryRelease(arg)) {
// 隊列頭節點 unpark
Node h = head;
if (h != null && h.waitStatus != 0)
// unpark AQS 中等待的線程
unparkSuccessor(h);
return true;
}
return false;
}
**************************************************************************
protected final boolean tryRelease(int releases) {
// state--,重入的線程釋放時的releases爲重入的次數
int c = getState() - releases; //1-1
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 支持鎖重入, 只有 state 減爲 0, 才釋放成功
if (c == 0) {
free = true;
//設置 exclusiveOwnerThread 爲 null
setExclusiveOwnerThread(null);
}
//設置鎖爲正常狀態0
setState(c);
return free;
}
此時,阻塞隊列不爲 null,並且 head 的 waitStatus = -1,進入 unparkSuccessor 流程
- 找到隊列中離 head 最近的一個 Node(沒取消的),unpark 恢復其運行,本例中即爲線程2
private void unparkSuccessor(Node node) {
// 如果狀態爲 Node.SIGNAL 嘗試重置狀態爲 0
// 不成功也可以
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);
}
回到線程2 的 acquireQueued 流程(死循環睡眠):
for (;;) {
//獲取當前線程節點的前驅
final Node p = node.predecessor();
//如果正好排到自己且競爭鎖成功
if (p == head && tryAcquire(arg)) {
setHead(node);
**************************************************************************
private void setHead(Node node) {
//head 指向剛剛線程2所在的 Node
head = node;
//node作爲哨兵
node.thread = null;
node.prev = null;
}
**************************************************************************
//原本的head從鏈表斷開,而可被垃圾回收
p.next = null; // help GC
failed = false;
return interrupted;
}
如果加鎖成功(沒有競爭),會設置
-
exclusiveOwnerThread 爲線程2,state = 1
-
head 指向剛剛線程2所在的 Node
-
原本的 head 因爲從鏈表斷開,而可被垃圾回收
此時,數據結構是這樣的:
注意:如果這時候有其它線程來競爭(非公平的體現),在Sync狀態設置爲0時,其他線程是可以繞過阻塞隊列直接佔有這把鎖的
如果被別的線程“鑽了空子”,則
Thread-4 被設置爲 exclusiveOwnerThread,state = 1
Thread-1 再次進入 acquireQueued 流程,獲取鎖失敗,重新進入 park 阻塞
可重入鎖實現源碼註釋
static final class NonfairSync extends Sync { // ... // Sync 繼承過來的方法, 方便閱讀, 放在此處 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()) { // state++ int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // Sync 繼承過來的方法, 方便閱讀, 放在此處 protected final boolean tryRelease(int releases) { // state-- int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 支持鎖重入, 只有 state 減爲 0, 才釋放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } }
可打斷鎖實現源碼註釋
不可打斷模式
在此模式下,即使它被打斷,仍會駐留在 AQS 隊列中,一直要等到獲得鎖後方能得知自己被打斷了
// Sync 繼承自 AQS static final class NonfairSync extends Sync { // ... private final boolean parkAndCheckInterrupt() { // 如果打斷標記已經是 true, 則 park 會失效 LockSupport.park(this); // interrupted 會清除打斷標記 return Thread.interrupted(); } 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; failed = false; // 還是需要獲得鎖後, 才能返回打斷狀態 return interrupted; } if ( shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt() ) { // 如果是因爲 interrupt 被喚醒, 返回打斷狀態爲 true interrupted = true; } } } finally { if (failed) cancelAcquire(node); } } public final void acquire(int arg) { if ( !tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ) { // 如果打斷狀態爲 true selfInterrupt(); } } static void selfInterrupt() { // 重新產生一次中斷 Thread.currentThread().interrupt(); } }
可打斷模式
static final class NonfairSync extends Sync { public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 如果沒有獲得到鎖, 進入 ㈠ if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } // ㈠ 可打斷的獲取鎖流程 private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) { // 在 park 過程中如果被 interrupt 會進入此 // 這時候拋出異常, 而不會再次進入 for (;;) throw new InterruptedException(); } } } finally { if (failed) cancelAcquire(node); } } }
公平鎖實現源碼註釋
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } // AQS 繼承過來的方法, 方便閱讀, 放在此處 public final void acquire(int arg) { if ( !tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ) { selfInterrupt(); } } // 與非公平鎖主要區別在於 tryAcquire 方法的實現 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 先檢查 AQS 隊列中是否有前驅節點, 沒有才去競爭 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; } // ㈠ AQS 繼承過來的方法, 方便閱讀, 放在此處 public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; // h != t 時表示隊列中有 Node return h != t && ( // (s = h.next) == null 表示隊列中還有沒有老二 (s = h.next) == null || // 或者隊列中老二線程不是此線程 s.thread != Thread.currentThread() ); } }
條件變量實現原理
每個條件變量其實就對應着一個等待隊列,其實現類是 ConditionObject
await 流程
開始 Thread-0 持有鎖,調用 await,進入 ConditionObject 的 addConditionWaiter 流程
創建新的 Node 狀態爲 -2(Node.CONDITION),關聯 Thread-0,加入等待隊列尾部
接下來進入 AQS 的 fullyRelease 流程,釋放同步器上的鎖
unpark AQS 隊列中的下一個節點,競爭鎖,假設沒有其他競爭線程,那麼 Thread-1 競爭成功
park 阻塞 Thread-0
signal 流程
假設 Thread-1 要來喚醒 Thread-0
進入 ConditionObject 的 doSignal 流程,取得等待隊列中第一個 Node,即 Thread-0 所在 Node
執行 transferForSignal 流程,將該 Node 加入 AQS 隊列尾部,將 Thread-0 的 waitStatus 改爲 0,Thread-3 的waitStatus 改爲 -1
Thread-1 釋放鎖,進入 unlock 流程,略
源碼
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
// 第一個等待節點
private transient Node firstWaiter;
// 最後一個等待節點
private transient Node lastWaiter;
public ConditionObject() { }
// ㈠ 添加一個 Node 至等待隊列
private Node addConditionWaiter() {
Node t = lastWaiter;
// 所有已取消的 Node 從隊列鏈表刪除, 見 ㈡
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 創建一個關聯當前線程的新 Node, 添加至隊列尾部
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
// 喚醒 - 將沒取消的第一個節點轉移至 AQS 隊列
private void doSignal(Node first) {
do {
// 已經是尾節點了
if ( (firstWaiter = first.nextWaiter) == null) {
lastWaiter = null;
}
first.nextWaiter = null;
} while (
// 將等待隊列中的 Node 轉移至 AQS 隊列, 不成功且還有節點則繼續循環 ㈢
!transferForSignal(first) &&
// 隊列還有節點
(first = firstWaiter) != null
);
}
// 外部類方法, 方便閱讀, 放在此處
// ㈢ 如果節點狀態是取消, 返回 false 表示轉移失敗, 否則轉移成功
final boolean transferForSignal(Node node) {
// 如果狀態已經不是 Node.CONDITION, 說明被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 加入 AQS 隊列尾部
Node p = enq(node);
int ws = p.waitStatus;
if (
// 上一個節點被取消
ws > 0 ||
// 上一個節點不能設置狀態爲 Node.SIGNAL
!compareAndSetWaitStatus(p, ws, Node.SIGNAL)
) {
// unpark 取消阻塞, 讓線程重新同步狀態
LockSupport.unpark(node.thread);
}
return true;
}
// 全部喚醒 - 等待隊列的所有節點轉移至 AQS 隊列
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
// ㈡
private void unlinkCancelledWaiters() {
// ...
}
// 喚醒 - 必須持有鎖才能喚醒, 因此 doSignal 內無需考慮加鎖
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
// 全部喚醒 - 必須持有鎖才能喚醒, 因此 doSignalAll 內無需考慮加鎖
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
// 不可打斷等待 - 直到被喚醒
public final void awaitUninterruptibly() {
// 添加一個 Node 至等待隊列, 見 ㈠
Node node = addConditionWaiter();
// 釋放節點持有的鎖, 見 ㈣
int savedState = fullyRelease(node);
boolean interrupted = false;
// 如果該節點還沒有轉移至 AQS 隊列, 阻塞
while (!isOnSyncQueue(node)) {
// park 阻塞
LockSupport.park(this);
// 如果被打斷, 僅設置打斷狀態
if (Thread.interrupted())
interrupted = true;
}
// 喚醒後, 嘗試競爭鎖, 如果失敗進入 AQS 隊列
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
// 外部類方法, 方便閱讀, 放在此處
// ㈣ 因爲某線程可能重入,需要將 state 全部釋放
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
// 打斷模式 - 在退出等待時重新設置打斷狀態
private static final int REINTERRUPT = 1;
// 打斷模式 - 在退出等待時拋出異常
private static final int THROW_IE = -1;
// 判斷打斷模式
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
// ㈤ 應用打斷模式
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
// 等待 - 直到被喚醒或打斷
public final void await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 添加一個 Node 至等待隊列, 見 ㈠
Node node = addConditionWaiter();
// 釋放節點持有的鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果該節點還沒有轉移至 AQS 隊列, 阻塞
while (!isOnSyncQueue(node)) {
// park 阻塞
LockSupport.park(this);
// 如果被打斷, 退出等待隊列
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 退出等待隊列後, 還需要獲得 AQS 隊列的鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 所有已取消的 Node 從隊列鏈表刪除, 見 ㈡
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 應用打斷模式, 見 ㈤
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
// 等待 - 直到被喚醒或打斷或超時
public final long awaitNanos(long nanosTimeout) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// 添加一個 Node 至等待隊列, 見 ㈠
Node node = addConditionWaiter();
// 釋放節點持有的鎖
int savedState = fullyRelease(node);
// 獲得最後期限
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
// 如果該節點還沒有轉移至 AQS 隊列, 阻塞
while (!isOnSyncQueue(node)) {
// 已超時, 退出等待隊列
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
// park 阻塞一定時間, spinForTimeoutThreshold 爲 1000 ns
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 如果被打斷, 退出等待隊列
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
// 退出等待隊列後, 還需要獲得 AQS 隊列的鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 所有已取消的 Node 從隊列鏈表刪除, 見 ㈡
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// 應用打斷模式, 見 ㈤
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
// 等待 - 直到被喚醒或打斷或超時, 邏輯類似於 awaitNanos
public final boolean awaitUntil(Date deadline) throws InterruptedException {
// ...
}
// 等待 - 直到被喚醒或打斷或超時, 邏輯類似於 awaitNanos
public final boolean await(long time, TimeUnit unit) throws InterruptedException {
// ...
}
// 工具方法 省略 ...
}