前言
上篇文章Java并发编程之Condition(一)介绍了await
方法的执行流程,需要着重关注的有以下几点:
- 如果先调用了
interrupt
方法,然后调用await会直接抛出异常; await
方法和lock
方法分别有一个等待队列;interrupt
和signal
都能从await
中唤醒等待的线程。
这篇文章主要来介绍下signal
的源码执行流程:
signal
// ConditionObject类
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
复制代码
调用signal
方法,首先会检查当前线程是否是持有锁的线程,如果不是那就没有资格进行唤醒,直接抛出异常。 然后获取头节点,如果头节点不是null
的话就唤醒它。
signal
方法需要在获取锁之后调用,否则抛出IllegalMonitorStateException
异常
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
复制代码
唤醒头节点需要把它从条件等待队列中移除掉,上面的if语句首先把头节点指向下一位,如果下一位是null
,表示队列中已经没有节点了,所以把尾节点置为null
,而头节点的下一个节点自然也是null
了。
while
循环条件的意思是,如果当前的头节点从条件等待队列转换到同步队列中失败了,而且下一个节点不是null
,那么就唤醒下一个节点,直到唤醒成功一个节点或者遍历完整个队列才结束。
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
复制代码
节点从条件等待队列转换到同步队列中,首先通过CAS
修改waitStatus
,如果成功了就可以通过enq
方法把节点添加到同步队列的末尾,并把前驱节点返回。
因为同步队列中的节点需要被前驱节点唤醒,如果前驱节点被取消(waitStatus>0
)或者把waitStatus
修改成Node.SIGNAL
(-1
)失败了,就表示前一个节点没法唤醒当前线程,所以此时就直接通过unpark
方法来唤醒当前线程,避免永远无法被唤醒。
总结
signal
方法的流程比await
方法简单多了,主要就做了两件事:
- 把当前线程的节点从条件队列中移除掉;
- 把当前线程的节点添加到同步阻塞队列的末尾。