Java并发编程之Condition(二)

前言

上篇文章Java并发编程之Condition(一)介绍了await方法的执行流程,需要着重关注的有以下几点:

  • 如果先调用了interrupt方法,然后调用await会直接抛出异常;
  • await方法和lock方法分别有一个等待队列;
  • interruptsignal都能从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方法简单多了,主要就做了两件事:

  • 把当前线程的节点从条件队列中移除掉;
  • 把当前线程的节点添加到同步阻塞队列的末尾。

猜你喜欢

转载自juejin.im/post/7229991597085622328
今日推荐