任意一個java對象,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、wait(long
timeOut)、notify()、notifyAll()方法,這些方法與synchronized同步關鍵字配合,可以實現等待/通知模式.condition接口也提供了類似Object的監視器方法,與Lock配合可以實現等待/通知模式,但是這倆者在使用方法以及功能特性上還是有差別的
以上摘自《java 併發編程的藝術》5.6節
condition相對於Object監視器方法最大的優勢在於, condition能設置多個等待隊列,這意味着利用condition能夠設計出更加靈活、性能更好的數據結構
例如:阻塞隊列ArrayBlockingQueue
//基於lock接口的顯示鎖
final ReentrantLock lock;
//針對消費者的等待隊列
private final Condition notEmpty;
//針對生產者的等待隊列
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
//重點看這裏,通過同一個監視器鎖創建出倆個不同的等待隊列
//這樣可以根據不同的條件判斷,來精準的使用具體的等待隊列,避免不必要的線程等待喚醒操作
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
//看下具體的使用,生產者生產對象
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//重點看這裏,如果判斷隊列已滿,那麼通知該生產者線程等待
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//重點看這裏,精準的喚醒一個消費者線程,上面的生產者線程不受影響
//如果使用notify()的話,並不知道喚醒的是生產者線程還是消費者線程
//如果使用notifyAll()喚醒所有線程,又會造成大量的鎖競爭,資源浪費
notEmpty.signal();
}
接下來進入正題,看下condition是如何實現的,先來看下condition接口定義了哪些方法
//相當於Object.wait(),設置當前線程處於等待狀態,響應中斷,拋出InterruptedException
void await() throws InterruptedException;
//await 忽略中斷
void awaitUninterruptibly();
//await 加上超時機制,響應中斷,返回 nanosTimeout-消耗時間,如果返回<=0,說明已超時
long awaitNanos(long nanosTimeout) throws InterruptedException;
//await 加上超時機制,並且可以指定時間單位,響應中斷
boolean await(long time, TimeUnit unit) throws InterruptedException;
//await 指定時間界限,在此時間前被通知喚醒則返回true,否則爲false,響應中斷
boolean awaitUntil(Date deadline) throws InterruptedException;
//喚醒一個等待線程,前提是正在執行該方法的線程已經獲取了condtion相對應的鎖
void signal();
//喚醒所有等待線程
void signalAll();
AQS的內部類ConditionObject實現了condition接口
//condtion 和同步鎖隊列是共用同一個node對象
//condtion的等待隊列中存放了所有調用該condition.await 方法的線程
//當調用condtion.signal 方法,等待隊列中的節點將會挪到同步鎖隊列中
public class ConditionObject implements Condition, java.io.Serializable {
//首節點
private transient Node firstWaiter;
//尾節點
private transient Node lastWaiter;
}
//在看下node中 condtion特有的參數
static final class Node {
//針對於condtion節點,初始狀態爲CONDITION
static final int CONDITION = -2;
//和同步器共用的一個參數,在同步器中代表node的模式(獨佔、共享)
//condtion中表示指向下一個節點,condtion是一個單向鏈表
volatile int waitStatus;
}
下面通過圖示來表達同步鎖隊列和 等待隊列之間的關聯
看具體的源碼await
//主要邏輯
//1、在condition隊列中添加一個節點,執行Release釋放自身同步狀態
//2、循環判斷該節點是否被轉移到鎖同步隊列,如果沒有則掛起等待
//3、如果被中斷,則跳出2的循環做出相應響應(這個響應要在重新獲取同步狀態後才能做)
//第一個過程是在獲取同步狀態的情況下執行,所以不用考慮併發情況,
//後倆個過程因爲已經釋放同步狀態了,需要考慮併發
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//在condition隊列中添加一個節點,狀態是condition
Node node = addConditionWaiter();
//執行release,如果失敗了,拋出IllegalMonitorStateException
//如果這裏成功,則表示該線程已經成功釋放了同步狀態,下面的流程都需要考慮併發情況了
int savedState = fullyRelease(node);
//這裏初始狀態爲0
//1、當該節點被移到同步鎖隊列之前中斷(signal之前),則interruptMode爲THROW_IE,最終會拋出InterruptedException
//2、當該節點在移動同步鎖隊列之後中斷(signal之後),則interruptMode爲REINTERRUPT,保留interrupt狀態
//3、如果沒有被中斷,保持原有狀態0
int interruptMode = 0;
//循環判斷該節點是否被移動到同步鎖隊列,如果沒有則掛起等待
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//如果是被中斷了,則將節點轉移到同步鎖隊列,同時跳出循環
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//進行到這裏說明節點已經被轉移到同步鎖隊列了,執行acquireQueued
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//執行到這裏,說明acquire成功,已經重新獲取了同步狀態
if (node.nextWaiter != null) // clean up if cancelled
//清理condtion等待隊列中的CANCELLED節點
unlinkCancelledWaiters();
//如果不爲0,說明之前該線程中斷過,那麼做出相應的響應
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//判斷該節點在同步鎖隊列中是否存在
final boolean isOnSyncQueue(Node node) {
//當狀態爲CONDITION時,或者當pre(同步器隊列用到的前驅)爲null,則說明不存在
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//next是同步器用到的後繼節點,如果不爲空,那麼該節點就肯定在同步鎖隊列了
//這裏很有趣,下面會具體分析爲什麼沒有node.prev!=null
if (node.next != null)
return true;
//從同步鎖隊列尾部查找是否有這個節點
return findNodeFromTail(node);
}
isOnSyncQueue方法有個很有趣的地方,if (node.next != null) 可以判斷節點肯定已經在同步鎖隊列了,那麼同理node.prev != null 是不是也能證明呢
答案當然不是了,看以下代碼
//這個方法是aqs往同步鎖隊列添加節點
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果未初始化隊列,創建head,cas賦值
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//重點在這裏,是先把該節點的先驅和尾節點關聯上,再使用cas
//如果這個過程中cas失效了,那麼該節點就添加失敗了,那這個關聯當然也就是無效的
//而signal時會調用這個方法,所以不能通過node的pre是否爲null來判斷節點是否已經成功移到同步鎖隊列
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
其中還有一個比較重要的方法是checkInterruptWhileWaiting,判斷線程有沒有收到中斷訊號、中斷的時機(signal前還是signal後)
private int checkInterruptWhileWaiting(Node node) {
//當檢測到線程中斷信號則執行transferAfterCancelledWait,否則返回0
//transferAfterCancelledWait邏輯主要判斷中斷的時機
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
//如果cas修改狀態成功了,說明此時還沒有收到signal信號
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//將該節點轉移到同步鎖隊列
//需要注意的是,雖然執行到這裏註定了該線程會被拋出InterruptedException
//但是也要等到獲取同步狀態之後才能拋出,否則會產生線程安全問題
enq(node);
return true;
}
//上面cas失敗說明已經收到signal信號,singnal方法已經執行
//但是也有可能沒執行完,也就是節點還正在轉移過程中,所以進行自旋等待
//返回false表明該線程只會記錄中斷狀態,不會拋錯
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
到這裏await基本講完了,awaitUninterruptibly、awaitNanos原理基本一樣就不多描述了,接下來看看signal
public final void signal() {
//這裏需要注意,isHeldExclusively是一個空方法,需要用戶自己判斷正在執行的線程是否已經獲得同步
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
//需用用戶繼承重寫,一般的邏輯就是判斷當前線程是否獲得同步狀態,
//這樣接下來的操作就是線程安全的,否則會帶來線程安全隱患
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
private void doSignal(Node first) {
do {
//頭節點出隊,重新設置nextWaiter爲頭節點
if ( (firstWaiter = first.nextWaiter) == null)
//如果隊列爲空,設置lastWaiter爲null
lastWaiter = null;
//原頭節點斷開後繼關聯
first.nextWaiter = null;
//transferForSignal失敗表示該節點signal失敗,繼續下一個節點
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//如果這裏cas失敗,表示這個節點狀態是cancel,那麼直接返回找下一個節點
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//將該節點放入同步鎖隊列
//注意,這裏返回的p是node的前驅節點
Node p = enq(node);
int ws = p.waitStatus;
//如果前驅節點是cancel,或者設置SIGNAL失敗,則喚醒本節點處理
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
完