ConditionObject
同样有一个队列,且保持队列的头结点和尾结点:
- Node firstWaiter
- Node lastWaiter
ConditionObject
对外暴露signal
和await
方法用于唤醒和等待。对于竞争资源的线程来讲,需要先等待再唤醒。无论是signal
还是和await
,都需要当前线程持有锁才能操作。
对于await
来讲,应该是当前线程主动阻塞,然后将当前线程加入到等待队列中,等待其他线程唤醒。而唤醒signal
应该是将这些主动阻塞的线程唤醒,给他们竞争锁的机会。
那么先看await
。
源码:
public final void await() throws InterruptedException {
//如果线程中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//进入等待队列中
Node node = addConditionWaiter();
//释放当前线程持有的同步器
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果不在同步队列中,那么直接阻塞当前线程;直到被唤醒时,加入到同步队列中
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//此时已经被唤醒,那么尝试获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//如果节点中断取消,那么清除节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
大致逻辑:
- 判断线程是否中断,如果中断抛出异常
- 将线程入
condition
队列,释放当前线程持有的锁,并获取当前同步器状态 - 判断节点是否在同步队列中,如果不在,阻塞当前线程。直到被唤醒或者中断
- 使用保存的同步器状态重新获取锁
- 如果在3中发生中断,那么抛出中断异常
addConditionWaiter
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
addConditionWaiter
将一个节点添加到condition
队列中。在入队时,判断当前尾节点是不是CONDITION
。如果不是则判断当前尾节点已经被取消,将当前节点出队。那么也就是说在队列中的节点状态,要么是CONDITION
,要么是CANCELLED
。具体的节点状态变化逻辑后面再看。
unlinkCancelledWaiters
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
方法的作用是移除取消的节点。方法本身只有在持有锁的时候会被调用。方法会遍历当前condition
队列,将所有非Condition
状态的节点移除。思考:为什么要遍历所有节点 且 取消所有的节点呢?有节点取消的时候,从队列中移除这个节点不就可以了?为什么要从头到尾遍历所有的节点,保持队列的整洁呢?
因为是链表队列,所以如果取消指定节点,需要从头遍历到指定节点,然后才能取消。在节点取消发生次数加多的情况下,会重复遍历队列。而如果将所有无效节点取消,那么节点取消时,只改变自身节点的状态即可。
fullyRelease
在节点添加到Condition
队列后,获取当前Sync
队列的状态。此处的作用是尝试释放锁,如果释放成功则返回节点状态;如果失败,那么当前节点取消。(节点如果await
,那么其持有的锁应该释放,让其他线程去持有锁)
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;
}
}
isOnSyncQueue
在释放锁之后,随即是一个循环,判断当前节点是否在队列中,如果不在队列中,则挂起当前线程。
final boolean isOnSyncQueue(Node node) {
/* ConditionObject中的节点并没有定义前置节点和后继节点,且节点的有效状态为CONDITION。而Sync中的节点,有前置节点和后继节点,且必有前继节点(dunmy node)。
*/
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
总共是三个条件来判断是否在队列中:
- 节点状态是否是
CONDITION
或者节点是否有前继节点 - 在不满足的时候,节点的后继节点不为空
- 在不满足1和2的时候,从尾部向前遍历可以查到当前节点。
条件1只是说,如果存在状态为CONDITION
或者有前继节点,那么这个节点肯定不在队列中。具体原因看注释
条件2说 节点的状态不是CONDITION
并且 节点有前继节点 并且 节点有后继节点,那么节点一定在同步队列中。为什么一定要有前继和后继才算是在同步队列中?少一个行不行?
可以只有后继节点。因为AQS中的节点如果有next
,那么一定在队列中。但是反之,如果在队列中, 不一定有next
。比如tail
节点。而如果只有前继节点,并不能保证是在同步队列中。具体原因可以看cancelAcquire
方法。方法中并没有完全解除取消节点的prev
。
findNodeFromTail
findNodeFromTail 比较简单,不做过多理解
private boolean findNodeFromTail(Node node) {
Node t = tail;
//从尾部遍历clh队列
for (;;) {
//找到了当前节点
if (t == node)
return true;
//没有知道当前节点
if (t == null)
return false;
t = t.prev;
}
}
checkInterruptWhileWaiting
线程已经被阻塞,那么如果线程被唤醒有两种情况:被signal
唤醒 或者 线程中断了。所以在线程被唤醒之后,检查线程是否中断。
在循环过程中还会检查当前线程是否中断,如果中断了,取消循环。对于checkInterruptWhileWaiting
则是检查是否在park
的时候被中断唤醒,具体实现在transferAfterCancelledWait
中,返回true表示中断在signal之前,那么需要抛出中断异常;如果返回false,说明中断在single之后,此时只需要响应中断即可。
ConditionObject
为中断响应定义个两种模式:
- REINTERRUPT 1 重新响应中断
- THROW_IE -1 抛出中断异常
- 0 没有中断
看transferAfterCancelledWait
做了什么:
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
signal
源码:
public final void signal() {
//如果当前线程持有锁且为独占模式
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取头结点且唤醒头结点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal:
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
简单逻辑说明:从ConditionObject
的队列从头开始遍历,有效唤醒第一个节点
transferForSignal:
final boolean transferForSignal(Node node) {
//源码注释:如果不能改变状态,说明节点已经取消
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将当前节点加入到等待队列中,并尝试将前置节点的状态设置为`SIGNAL`,如果失败,直接唤醒当前进程
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}