JUC并发包源码阅读之await()和signal()

await()

在java多线程中,有多种方式等待、唤醒,有wait()和notify以及await()和signal(),wait()和notify()是在Object中使用的,通常用于synchronized,而await()和signal()通常在lock()中使用。线程在获得锁的前提下,调用await()会使线程释放锁并加入到Condition等待队列阻塞,直到接收到signal()命令,重新加入到同步队列。除了使用方法上的不同,两种还有特性上的不同,在等待队列中的线程可以响应中断,而wait()不支持;可以创建多个condition等待队列,而wait()不支持。
下面我们就来分析一下await()和signal()的源码

/**
 * Implements interruptible condition wait.
 * <ol>
 * <li> If current thread is interrupted, throw InterruptedException.
 * <li> Save lock state returned by {@link #getState}.
 * <li> Invoke {@link #release} with saved state as argument,
 *      throwing IllegalMonitorStateException if it fails.
 * <li> Block until signalled or interrupted.
 * <li> Reacquire by invoking specialized version of
 *      {@link #acquire} with saved state as argument.
 * <li> If interrupted while blocked in step 4, throw InterruptedException.
 * </ol>
 */
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter(); //加入condition等待队列
    int savedState = fullyRelease(node); //释放节点上的锁 
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { //不在同步队列上就一直循环
        LockSupport.park(this); //挂起当前线程
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) //检查在condition等待队列期间有无被中断
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE) //释放同步队列的节点
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters(); //清除condition队列上waitStatus不为CONDITION的节点
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

从中可以看出,await()方法就是将节点放入condition队列中,然后释放该线程上的锁,然后这段程序会一直循环判断该节点是否在同步队列中,如果不在同步队列中,就会park挂起该线程并检查该线程是否中断,并进入下一次循环,如果在同步队列中(表明可能调用了signal()通知了线程),会调用acquireQueued()方法使同步队列中的节点获得锁。简单的用一句话概括就是,使用await()会让节点释放锁并加入condition等待队列,直到其他线程调用signal()或者达到定时的时间,加入同步队列获得锁。
接下来看看addConditionWaiter()方法,这个方法就是将节点加入condition等待队列中。

/**
 * Adds a new waiter to wait queue.
 * @return its new wait node
 */
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) { //如果lastWaiter的ws不为condition,就清除condition队列中不为condition的节点
        unlinkCancelledWaiters();
        t = lastWaiter; //重新赋值一个ws为condition的节点为lastWaiter
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION); //将该节点waitStatus设为CONDITION并加入等待队列
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

然后,调用fullyRelease()释放线程上的锁,需要注意的是,如果该线程没有获得锁,会抛出IllegalMonitorStateException,如果释放锁失败了,会将节点的waitStatus设为CANCELLED

/**
 * Invokes release with current state value; returns saved state.
 * Cancels node and throws exception on failure.
 * @param node the condition node for this wait
 * @return previous sync state
 */
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()

 // Internal support methods for Conditions
	
	/**
	 * Returns true if a node, always one that was initially placed on
	 * a condition queue, is now waiting to reacquire on sync queue.
	 * @param node the node
	 * @return true if is reacquiring
	 */
	final boolean isOnSyncQueue(Node 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;
	    /*
	     * node.prev can be non-null, but not yet on queue because
	     * the CAS to place it on queue can fail. So we have to
	     * traverse from tail to make sure it actually made it.  It
	     * will always be near the tail in calls to this method, and
	     * unless the CAS failed (which is unlikely), it will be
	     * there, so we hardly ever traverse much.
	     */
	    return findNodeFromTail(node);
	}

在这个方法中,我们可以看到如果节点的next()不为空就认为该节点在同步对列中,但节点的prev不为空,就不能作为节点在同步队列的依据。方法中的注释中说到,node.prev can be non-null, but not yet on queue because the CAS to place it on queue can fail. So we have to traverse from tail to make sure it actually made it.大概意思就是cas放置该节点有可能会失败,所以必须的遍历队列来确定节点在队列中。明显可知findNodeFromTail()就是从后往前遍历队列,确定节点是否在队列中。
如果节点不在同步队列中,会调用LockSupport.park(this) 挂起当前线程,并且调用checkInterruptWhileWaiting()来查看在等待队列期间是否被中断,如果被中断则退出循环

/**
 * Checks for interrupt, returning THROW_IE if interrupted
 * before signalled, REINTERRUPT if after signalled, or
 * 0 if not interrupted.
 */
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

/**
 * Transfers node, if necessary, to sync queue after a cancelled wait.
 * Returns true if thread was cancelled before being signalled.
 *
 * @param node the node
 * @return true if cancelled before the node was signalled
 */
final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { //此时其他线程还没有调用signal()
        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)) //其他线程调用了signal(),现在需要等待这个线程尾插进同步队列成功
        Thread.yield();
    return false;
}

从中我们可以看出,当中断线程时,此时其他线程还没有调用signal(),将线程加入同步队列并interruptMode会被赋值为THROW_IE,其他线程调用了signal(),等待线程尾插同步队列成功并线程interruptMode会被赋值为REINTERRUPT。
在await()方法的最后,如果condition等待队列中还有其他节点,会调用unlinkCanceledWaiters()方法清除等待队列中waitStatus不为CONDITION的节点,最后根据interruptMode抛出对应的异常。

/**
 * Unlinks cancelled waiter nodes from condition queue.
 * Called only while holding lock. This is called when
 * cancellation occurred during condition wait, and upon
 * insertion of a new waiter when lastWaiter is seen to have
 * been cancelled. This method is needed to avoid garbage
 * retention in the absence of signals. So even though it may
 * require a full traversal, it comes into play only when
 * timeouts or cancellations occur in the absence of
 * signals. It traverses all nodes rather than stopping at a
 * particular target to unlink all pointers to garbage nodes
 * without requiring many re-traversals during cancellation
 * storms.
 */
private void unlinkCancelledWaiters() { //清除condition队列中节点ws不为condition的节点
    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;
    }
}

await()思维导图

仅供参考

signal()和signalAll()

我们已经了解了如何将线程置为等待,现在来看看怎么唤醒线程,await()对应的唤醒线程有两种方式,signal()和signalAll(),从字面意思上我们就可以知道signalAll()就是唤醒所有的线程,唤醒所有线程会将condition等待队列上的所有线程都加入到同步队列中。

/**
 * Moves the longest-waiting thread, if one exists, from the
 * wait queue for this condition to the wait queue for the
 * owning lock.
 *
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
 *         returns {@code false}
 */
public final void signal() {
    if (!isHeldExclusively()) //锁不被当前线程持有
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

/**
 * Moves all threads from the wait queue for this condition to
 * the wait queue for the owning lock.
 *
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
 *         returns {@code false}
 */
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

我们从两者的代码中发现,在进行唤醒之前,都会先调用isHeldExcusively()方法先判断,就是先判断锁是否被当前线程占有。

protected final boolean isHeldExclusively() {
	// While we must in general read state before owner,
	// we don't need to do so to check if current thread is owner
	return getExclusiveOwnerThread() == Thread.currentThread();
}

现在来分析doSignal()和doSignalAll()的差异

/**
 * Removes and transfers nodes until hit non-cancelled one or
 * null. Split out from signal in part to encourage compilers
 * to inline the case of no waiters.
 * @param first (non-null) the first node on condition queue
 */
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) && //cas设置ws从condition为0失败(可能ws!=condition)并且该节点的下一个节点不为空
             (first = firstWaiter) != null); //如果cas成功就执行一次,失败才循环
}

/**
 * Removes and transfers all nodes.
 * @param first (non-null) the first node on condition queue
 */
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

可以看出在signal()中只有当transferForSignal()失败并且condition等待队列中下一个节点不为空时对下一个节点调用transferForSignal(),也就是说当transferForSignal()成功或者节点为空就会退出循环。而signalAll()是如果节点不为空就依次对队列中的所有节点调用transferForSignal(),下面来看看transferForSignal()方法

/**
 * Transfers a node from a condition queue onto sync queue.
 * Returns true if successful.
 * @param node the node
 * @return true if successfully transferred (else the node was
 * cancelled before signal)
 */
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //CAS自旋设置CONDITION为0,若失败,返回false
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node); //成功就加入aqs队列,返回true
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) //前一个节点的ws为canceled或者cas设置为signal失败,释放线程
        LockSupport.unpark(node.thread);
    return true;
}

transferForSignal()方法中先cas尝试设置waitStatus从CONDITION为0,如果失败(可能这个节点的waitStatus不为CONDITION)则返回false,成功则将节点加入同步队列,加入同步队列后,这个节点在同步队列中的前一个节点的waitStatus如果为CANCELLED或者cas设置SIGNAL失败则unpark唤醒线程,以重新加入同步队列。

signal()和signalAll()思维导图

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅随荷0

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值