一,底層AQS源碼分析:併發編程(四):AbstractQueuedSynchronizer源碼分析
二,Condition介紹
1,線程通信
* 在 synchronized 中,有分析過通過 wait()/notify()/notifyAll() 實現線程通信。同樣,在JUC中,也提供了這樣的工具類,就是Conditon
2,類圖
* 從類圖中可以看出,Condition作爲線程通信的頂層類,派生出實現類 ConditionObject,該實現類爲AQS的內部類,並對Node做了擴展
* 從屬性中可以看出,ConditionObject 中新添加了兩個屬性 firstWaiter 和 lastWaiter,再關聯 Node 中已經定義好的 nextWaiter,組合成爲一個單向鏈表
* 所以在Condition中,除過繼承自AQS的FIFO雙向鏈表,Condition自身也實現了一個基於Node的單向鏈表。在調用 Condition.await() 時,線程節點被存儲到Condition單向鏈表形成的阻塞隊列中;線程被喚醒後,被從阻塞隊列釋放,並添加到AQS同步隊列中繼續進行鎖爭搶,如下圖
3,常用API
// 線程等待
void await() throws InterruptedException;
// 線程限時等待,分析其中一個
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 線程喚醒
void signal();
// 線程全部喚醒
void signalAll();
4,功能DEMO
package com.gupao.concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition測試
*
* @author pj_zhang
* @create 2019-10-08 21:39
**/
public class ConditionTest {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
// 等待線程首先執行,打印第一句話後等待
new Thread(() -> {
lock.lock();
System.out.println(Thread.currentThread().getName() + " ready await...");
try {
// 等待線程等待之後,會自動釋放鎖
condition.await();
// 等待線程被喚醒後,繼續打印第二句話
System.out.println(Thread.currentThread().getName() + " execute done...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 釋放鎖
lock.unlock();
}
}, "THREAD_AWAIT").start();
// 喚醒線程等待100毫秒,讓等待線程先執行
new Thread(() -> {
try {
Thread.sleep(100);
// 獲取到等待線程等待釋放的鎖,並打印第一句話
lock.lock();
System.out.println(Thread.currentThread().getName() + " ready signal...");
// 喚醒等待的線程,此時鎖依舊被喚醒線程持有
// 等待線程被喚醒後,會自動被添加到等待隊列
// 如果存在多個等待線程,需要多次signal喚醒
// signalall()可以喚醒全部等待線程,全部喚醒後在AQS隊列中等待執行
condition.signal();
System.out.println(Thread.currentThread().getName() + " execute done...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 執行完成後釋放鎖,由AQS同步隊列繼續爭搶獲取鎖
lock.unlock();
}
}, "THREAD_SIGNAL").start();
}
}
三,源碼分析
1,等待源碼分析
1.1,不帶時間等待源碼分析
* await():線程阻塞
public final void await() throws InterruptedException {
// 判斷線程是否中斷,直接跑移除處理
if (Thread.interrupted())
throw new InterruptedException();
// 添加線程到Condition阻塞隊列隊尾
Node node = addConditionWaiter();
// 釋放當前線程獲取到的鎖,並喚醒下一個線程
// 此處注意重入鎖,一次全釋放
int savedState = fullyRelease(node);
int interruptMode = 0;
// 從尾部開始尋找,判斷當前節點是否已經在AQS同步隊列中存在
while (!isOnSyncQueue(node)) {
// 如果存在,直接掛起,此處等待Signal
LockSupport.park(this);
// 被喚醒後,獲取中斷狀態,爲0表示沒有被中斷
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 如果不存在,或者掛起被喚醒後,繼續嘗試獲取鎖,
// 此處已經從Condition阻塞隊列中釋放,在AQS同步隊列中如果沒有直接獲取到鎖,會在AQS同步隊列中排隊等候
// acquireQueued如果返回true,即已經中斷,會進行&&後面判斷,如果狀態不是THROW_IE,則默認修改爲interruptMode
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
// 對失效狀態的節點進行一次清理
unlinkCancelledWaiters();
if (interruptMode != 0)
// 非0表示線程已經被中斷,進行中斷異常處理或者重新置爲中斷狀態
reportInterruptAfterWait(interruptMode);
}
* addConditionWaiter():添加線程到Condition阻塞隊列
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果當前節點已經失效,重組Condition阻塞隊列,去除無效節點
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;
}
* fullyRelease(Node node):釋放當前線程獲得的鎖,注意重入釋放
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;
}
}
public final boolean release(int arg) {
// 嘗試釋放arg次鎖
if (tryRelease(arg)) {
Node h = head;
// 釋放成功後,喚醒下一個節點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
* isOnSyncQueue(Node node):判斷當前節點是否已經在AQS同步隊列中存在
final boolean isOnSyncQueue(Node node) {
// CONDITION狀態的默認不在AQS同步隊列中存在
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null)
return true;
// 從根節點開始,進行Node比對
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
* =======================線程被 signal() 喚醒後,繼續執行如下代碼=========================
* checkInterruptWhileWaiting(Node node):校驗線程在等待中是否已經被中斷
private int checkInterruptWhileWaiting(Node node) {
// 線程是否已經中斷 ? (線程是否在等待時中斷?拋異常:重置中斷狀態):沒有中斷返回0
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
// 判斷線程是否在等待的時候被中斷,如果線程被signal()喚醒,狀態則會被修改爲0
// 此處存在await時間情況,可能會自喚醒
// 自喚醒如果發現中斷,直接異常處理!signal()喚醒,重置中斷狀態,交由業務代碼處理
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
// 線程被喚醒後中斷,且線程不在AQS隊列中,釋放線程調度時間,直到線程被加到AQS同步隊列中,返回false
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
* acquireQueued(final Node node, int arg):嘗試競爭鎖並添加到AQS同步隊列,AQS底層方法,不做分析
* unlinkCancelledWaiters():清除失效節點
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
// 定義一個臨時節點,進行數據傳遞
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
// 數據已經失效
if (t.waitStatus != Node.CONDITION) {
// 將t的上下指向置空,t節點掛空,等待GC回收
// 之後重新構建單向鏈表
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
* reportInterruptAfterWait(int interruptMode):線程中斷最終處理,根據前幾步中斷中斷標識進行處理,判斷拋異常還是重置狀態!
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
1.2,帶時間等待源碼分析
* await(long time, TimeUnit unit):帶時間等待,與直接等待差距不大,只是添加了超時喚醒機制
public final boolean await(long time, TimeUnit unit)
throws InterruptedException {
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
// 獲取最終超時時間
final long deadline = System.nanoTime() + nanosTimeout;
boolean timedout = false;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 剩餘時間小於0,則添加到AQS同步隊列等候
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
// 線程park指定納秒數,超時後自喚醒執行
LockSupport.parkNanos(this, nanosTimeout);
// 線程沒有中斷,跳出循環,進行鎖競爭執行
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
// 獲取生意時間
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return !timedout;
}
2,喚醒源碼分析
2.1,喚醒單個線程
* signal():單個線程喚醒
public final void signal() {
// 判斷當前線程是否是持有鎖的線程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 獲取Condition阻塞隊列頭節點進行喚醒,排隊連續喚醒
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
* doSignal(Node first):喚醒節點,注意當前步驟已經涉及了節點移除
private void doSignal(Node first) {
do {
// 如果Condition隊列中只有這一個節點,則把尾節點置空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 將頭節點的下一個節點置空
first.nextWaiter = null;
// 判斷當前節點可以被喚醒,狀態變更失敗,則繼續喚醒下一個節點
// 狀態變更失敗,可能已經自喚醒
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
* transferForSignal(Node node):是否轉變喚醒節點
final boolean transferForSignal(Node node) {
// 狀態變更失敗,線程失效
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// enq返回的是node的前置節點
Node p = enq(node);
int ws = p.waitStatus;
// 前置節點已經失效,喚醒當前線程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
2.2,喚醒全部線程
* signalAll():喚醒Condition阻塞隊列全部等待線程
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
// 喚醒全部線程
doSignalAll(first);
}
* doSignalAll(Node first):算是對doSignal()條件遍歷的精簡,無條件遍歷整個Condition鏈表,喚醒全部節點
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
// 從第一個節點開始向最後一個節點遍歷,喚醒所有有效的阻塞節點
} while (first != null);
}