AQS条件队列的创建,入队及出队

这时AQS的第三篇文章,在第二篇文章中,我们分析了同步队列的入队与出队,在第三盘文章中,我们去分析条件队列的创建,入队,出队。

ConditionObject 介绍

条件对象,在MESA模型中,条件对象可以有多个,每个条件对象都有一个对应的条件队列;当调用wait方法时,添加到条件队列;调用notify
方法时,从条件队列转移到同步队列;
在java中,ConditionObject 对象实现了Condition接口,提供await(),signal(),signalAll()等一系列等待唤醒方法;
ConditionObject 含有队首,队尾两个属性

		/** First node of condition queue. 队首 */
        private transient Node firstWaiter;
        /** Last node of condition queue. 队尾*/
        private transient Node lastWaiter;
      

且两个属性都没有关键字 volatile ,是因为条件队列只有在获取独占锁的情况下才可以入队;也意味着调用 await,signal等方法,需要在持有锁的情况下才可以调用;

条件队列的创建,入队

在同步队列创建时,有addWaiter方法,在条件队列创建中有 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的构造器,创建了一个节点为-2的等待队列节点
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
            	// 尾节点为空,则将当前节点作为首节点
                firstWaiter = node;
            else
                t.nextWaiter = node;
            // 当前节点赋值给为尾节点,尾插法
            lastWaiter = node;
            return node;
        }

总结看来,就是使用尾插法插入,使用nextWaiter构造一个单向链表;当队列尚未构造时,既尾节点为空时,则当前节点为头节点;

出队

再插入时,如果检测到尾节不是等待状态,则需要清理出队;同时,当条件队列转移到同步队列时,也会涉及到出队;先从见到过的 unlinkCancelledWaiters 分析;

清理取消的节点
 		/**
         * 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() {
        	// 获得头节点为当前节点
            Node t = firstWaiter;
            // 轨迹为null
            Node trail = null;
            // 当前节点不为null
            while (t != null) {
            	// 获取当前节点的下一个节点
                Node next = t.nextWaiter;
                // 如果当前节点被取消
                if (t.waitStatus != Node.CONDITION) {
                	// 清空当前节点的nextWaiter引用
                    t.nextWaiter = null;
                    // 如果上一次处理的节点为null,将下一个节点赋值为头节点
                    // 否则将下一个节点作为上一次节点的下一个节点
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    // 如果下一个节点为空,则尾节点为上一个节点
                    if (next == null)
                        lastWaiter = trail;
                }
                // 如果当前节点没有被取消,则上一次遍历节点赋值为当前节点,开始下一次循环
                else
                    trail = t;
                // 将下一个节点赋值为将要遍历的节点,继续循环
                t = next;
            }
        }

就是从头节点开始遍历,剔除取消的节点,维护着单项链表的链接,及头节点和尾节点的引用;

节点转移到同步队列前的出队
        /**
         * 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;
                // 将当前节点nextWaiter引用置为空
                first.nextWaiter = null;
                // 使当前节点入同步队列
                transferForSignal(first);
                // 下一个节点赋值给当前节点,进行下一次循环
                first = next;
            } while (first != null);
        }
         /**
         * Removes and transfers nodes until hit non-cancelled one or
         * 移除并转移节点直到命中一个非取消的等待节点
         * null. Split out from signal in part to encourage compilers
         * 从signal方法中分离出来作为一个单独方法,使为了鼓励编译器优化
         * to inline the case of no waiters.
         * (编译器优化部分,不做深入了解)
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {
            do {
            	// 如果当前节点的下一个节点为空,则将尾节点置为null,表明自己就是尾节点
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 清理当前节点的nextWaiter引用
                first.nextWaiter = null;
                // 转移失败且下一个节点不为空继续循环
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

在出队时,都有一种节点为取消的节点,下一节我们分析取消发生的时机;

等待节点的取消转移

类比同步队列取消 cancelAcquire 的方法,一般是在超时或者调用tryAcquire方法时失败时发生;我们研究下等待节点的取消;取消发生的时机,是在于await()超时时;
当调用不具有时间限制的await方法时,只会加入条件队列的尾部,等待signal信号转移到同步队列;而具有时间限制的await方法时,加入条件队列后,当超时后会尝试取消等待,尝试将状态切换到0;一旦无法完成cas状态切换,则当前线程会一直让出时间片,直到它已经被同步到同步队列上;代码分析详见 第二篇transferAfterCancelledWait方法解析;
与singal的区别在于,signal是先出条件队列,然后再入同步队列;而取消是不出条件队列,仅是将状态改为0,再入同步队列;

总结

目前,我们已经分析了条件队列出队,入队,取消的场景,与同步队列的交互也已经考察;下一篇我们将分析AQS的使用;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值