AbstractQueuedSynchronizer中条件(Condition)等待(await)、通知唤醒(signal)实现的源码分析

1. 概述

在AbstractQueuedSynchronizer中,有两个FIFO队列,一个是同步队列,用来排队申请同步状态,还有一个是条件等待队列,当调用了await()系列的方法后,就会在等待队列尾部插入一个节点,通知唤醒的时候会把这个节点从等待队列转移到同步队列。

本文主要描述条件等待队列以及等待、通知机制的实现,关于同步队列的相关操作和实现分析,可以在这篇博客了解。

2. 分析

ReentrantLock可以实现条件等待,我们可以先调用newCondition()方法生成一个条件对象ConditionObject,然后调用ConditionObject的await()方法即可实现条件等待。

一路跟踪源码到AbstractQueuedSynchronizer内部类ConditionObject的await()方法。

        /**
         * 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();
            //释放同步队列中当前节点持有的同步状态,在这里会将同步队列中对应的节点移除
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                //如果不在同步队列里,阻塞当前线程,等待唤醒
                LockSupport.park(this);
                //线程被唤醒,是否把这个节点添加到同步队列中,检查当前线程的interrupt中断标记
                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);
        }

首先进行的是addConditionWaiter()方法。

        /**
         * 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) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

这个方法中,先取到最后一个等待节点t,如果t不为null或者t的状态不为CONDITION,触发一次“删除取消等待的节点”操作,也就是unlinkCancelledWaiters()方法,只要节点的状态不为CONDITION,该节点就可以被认为取消等待了(等待结束)。删除结束,就新创建一个CONDITION状态的节点加入到队列末尾。注意条件等待队列和同步队列中的不同,条件等待队列中的节点只知道它的下一个节点,并不知道它的上一个节点。

到这里addConditionWaiter()方法结束,此时已经增加了一个节点到条件等待队列,并且将新增加的节点返回。再回到await()方法,接下来需要同步队列中当前持有同步状态的节点(头部节点,对应了当前线程)进行资源的释放,也就是fullyRelease()方法,进入fullyRelease(),其实就是调用了release()方法,并返回了释放前的加锁的次数。release()方法解析参见这篇博客

再返回到await()方法,接下来是一个while循环,判断这个节点是否在同步队列上,如果不在,就进入循环体,通过LockSupport.park()阻塞线程,当线程被唤醒,执行while中的if判断。

        /**
         * 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)) {
            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;
    }

如果当前线程被中断,则调用transferAfterCancelledWait决定外层await()方法是应该抛出InterruptedException异常还是重新调用中断方法。transferAfterCancelledWait()方法中,如果CAS设置成功,说明中断之前没有调用signal()方法(因为signal方法会对节点状态进行CAS设置,从CONDITION到0),将当前节点添加到同步队列;否则,循环检查当前节点是否在同步队列中,如果不在,就把CPU让出来,直到被放到同步队列上时,中止循环,返回false。

然后回到await()方法,调用acquireQueued(),acquireQueued()中进行自旋等待同步资源(这篇博客),让被唤醒的节点进行排队,并处理中断标记,如果有下一个条件等待者,这里会触发一次“取消等待节点清理”操作。再最后根据中断标记决定是抛出中断异常,还是仅仅调用中断方法。

接着在来看signal()方法,signal()方法唤醒等待的线程后,while中LockSupport.park(this);之后的代码才可以执行。

        /**
         * 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) &&
                     (first = firstWaiter) != null);
        }

        /**
         * 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()方法跟踪到doSignal()方法,这里主要进行的操作是,将等待队列中的第一个等待者移动到同步队列,当成一个全新的尾节点来进行处理,具体流程也是增加到尾节点、然后进行自旋获取(这篇博客),并且这里将firstWaiter指向原本firstWaiter的下一个等待者,使原本的firstWaiter成为了一个垃圾对象(可被回收)。

signalAll方法也很好理解,直接将所有的等待者唤醒。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值