AQS源码解析 5.Condition条件队列 await() & signal() 核心方法

AQS源码解析—Condition条件队列 await() & signal() 核心方法

简介

在 Condition 条件队列中使用的也是 AQS 中的 Node 结构,它并没有使用 prev 和 next 属性,而使用的是 nextWaiter 去实现了一个单向链表的结构:

Node nextWaiter;

流程图

条件队列流程图

image-20221120144615535

await() 流程图

image-20221027130055328

await() 节点出队重新构造入队流程图

在这里插入图片描述

signal() 流程图

在这里插入图片描述

signal() 节点迁移示意图

在这里插入图片描述

Condition 接口

  • 下面将 AQS 的队列简称为 同步队列,Condition 的队列(单向链表)简称为 等待队列
    • 同步队列就是阻塞队列,等待队列就是条件队列。
public interface Condition {
    
    // 阻塞 可以响应中断(抛出中断异常)
    void await() throws InterruptedException;

	// 阻塞 不响应中断
    void awaitUninterruptibly();
	
    // 线程超时等待,可以指定挂起时间,时间单位为纳秒
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    // 线程超时等待,可以指定挂起时间,时间单位可以自行设置
    boolean await(long time, TimeUnit unit) throws InterruptedException;
	
    // 线程等待,直到收到被唤醒、或收到中断信号、或到了指定的截止时间日期自动唤醒
    boolean awaitUntil(Date deadline) throws InterruptedException;
	
    // 唤醒条件队列中第一个可以被唤醒的节点(将等待队列的节点迁移到同步队列中)
    void signal();
	
    // 唤醒条件队列中的所有节点全部迁移到同步队列中
    void signalAll();
}

ConditionObject 实现类

	/*
	 * Condition 接口的实现类
	 * 此类是AQS中的一个内部类,等待队列使用的节点也是AQS的内部类 Node
	 * 只不过与AQS的队列不同的是,等待队列中是单向链表。AQS的队列是双向链表。
	 * 注意:普通内部类是可以访问到类外部的域(后面要做节点的迁移)
     */
	public class ConditionObject implements Condition, java.io.Serializable {
		private static final long serialVersionUID = 1173984872572414699L;
        
    	// 条件队列的第一个node节点(头节点)
        private transient Node firstWaiter;
        
    	// 条件队列的最后一个node节点(尾节点)
        private transient Node lastWaiter;
    	
    	// lock.newCondition() 最终调用的还是这个构造器
        // Lock => ReentrantLock => Sync => new Condition() => new ConditionObject()
   		public ConditionObject() { }

await() 方法

        public final void await() throws InterruptedException {
            // 判断当前线程是否是中断状态,如果是则直接抛出中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
            /*
             * addConditionWaiter()将当前线程封装成Node节点加入条件队列中
             * 并返回封装完成的Node节点(类似AQS中的addWaiter方法)
             */
            Node node = addConditionWaiter();
            /*
             * fullyRelease() 完全释放当前线程的锁(state置为0)
             * 因为这里要挂起等待被唤醒,所以必须先完全释放锁
             */
            int savedState = fullyRelease(node);
            /*
             *  0 表示在Condition队列挂起期间没有接收过中断信号
             * -1 表示在Condition队列挂起期间接收到了中断信号(THROW_IE)
             *  1 表示在Condition队列挂起期间接未接收到中断信号,但是迁移到“阻塞(同步)队列AQS的队列”之后,接收过中断信号(REINTERRUPT)
             */
            int interruptMode = 0;
            /*
             * isOnSyncQueue(node) 
             * true表示当前节点已经迁移到了同步队列中
             * false表示当前节点还在等待队列中如果当前节点还在等待队列中,则需要继续挂起。
             */
            while (!isOnSyncQueue(node)) {
                // 当前节点还在等待队列中,继续被挂起
                LockSupport.park(this);
                 /*
                 * 这里就是唤醒后判断是否是被中断唤醒的,然后执行响应的中断逻辑。
                 * 
                 * checkInterruptWhileWaiting()就算在等待队列挂起期间,
                 * 线程发生了中断,对应的node也会被迁移到同步队列(参考transferAfterCancelledWait)
                 * 如果当前node被中断过,也直接break。
                 * 
                 * 什么时候会被唤醒?
                 * 1.常规:外部线程获取锁后,调用了signal()方法,转移条件队列的头节点,到同步队列,当这个节点获取锁后,会被唤醒
                 * 2.转移到同步队列后,发现同步队列的前驱节点状态是取消状态,此时会直接唤醒当前节点
                 * 3.当前节点挂起期间,被外部线程使用中断唤醒..
                 */
                // 如果不为0,说明发生过中断了,也会被迁移到阻塞队列中,直接break。
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            
            /*
             * 到这里 说明当前node已经被迁移到了同步队列中
             */
            
            /*
             * 条件1:acquireQueued(node, savedState)就是在同步队列中竞争锁的逻辑 savedState作用:恢复加锁状态 需要把之前释放锁的状态设置回去
             *   返回true -> 表示在同步队列中被外部线程中断唤醒过
             *   返回false -> 表示在同步队列中没有被外部线程中断唤醒过
             * 条件2:interruptMode != THROW_IE 成立,表示node在等待队列未发生过中断
             *   两个条件同时成立,表示node在同步队列发生过中断。
             *   所以将interruptMode设置为REINTERRUPT(在同步队列发生过中断)
             */
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            // 考虑下 node.nextWaiter != null,条件什么时候成立呢?
            // 其实是node在条件队列内时如果被外部线程中断唤醒时,会加入到阻塞队列
            // 但是并未设置 nextWaiter = null,这里需要做一个清理工作。
            if (node.nextWaiter != null) // clean up if cancelled
                // 清理等待队列内取消状态的节点
                unlinkCancelledWaiters();
            /*
             * 条件成立:说明挂起期间发生过中断(1.条件队列的挂起 2.条件队列之外的挂起)
             */
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

addConditionWaiter()

        /*
         * 将当前线程封装成一个Node加入到等待队列的末尾。
         * (会将等待队列中所有处于取消状态的节点全部出队)
         * 调用await方法的线程 都是 持锁状态的,也就是说 addConditionWaiter 这里不存在并发!
         */
        private Node addConditionWaiter() {
            // 指向队尾
            Node t = lastWaiter;
            /*
             * 条件1:t != null 成立:说明当前条件队列中,已经有node元素了
             * 条件2:node 在条件队列中时,它的状态是CONDITION(-2)
             *       t.waitStatus != Node.CONDITION 成立:说明当前node发生中断了..处于取消状态CANCELLED(1)
             * 此时当前线程在队列中做一次清除处理,将所有处于取消状态的node全部干掉。
             */ 
            if (t != null && t.waitStatus != Node.CONDITION) {
                // 清理条件队列中所有取消状态CANCELLED(1)的节点
                unlinkCancelledWaiters();
                // 更新t指向最新的尾结点(lastWaiter可能会更新 因为可能处于取消状态CANCELLED(1))
                t = lastWaiter;
            }
            // 将当前线程封装为一个Node,Node状态(waitStatus)为Condition(-2)
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            // 下面就是入队的过程(这里只有加锁的线程可以进来,所以不需要加锁)
            // 条件成立:说明条件队列中没有任何元素,当前线程是第一个进入队列的元素
            if (t == null)
                // 让firstWaiter 指向当前node
                firstWaiter = node;
            //说明当前条件队列已经有其它node了 做追加操作
            else
                // 让nextWaiter 指向当前追加的node
                t.nextWaiter = node;
            // 更新队尾引用指向 当前node
            lastWaiter = node;
            // 最终返回构造的节点
            return node;
        }

fullyRelease()

        /*
         * 完全释放锁
         */
		final int fullyRelease(Node node) {
            // 完全释放锁是否成功,当failed失败时,说明当前线程是未持有锁调用 await方法的线程(错误写法)
        	// 假设失败,在finally代码块中 会将刚刚加入到 条件队列的 当前线程对应的node状态 修改为 取消状态
        	// 后继线程就会将 取消状态的节点给清理出去了
            boolean failed = true;
            try {
                // 获取当前的state的值
                int savedState = getState();
                /*
                 * 全部释放掉state,变为初始状态0 这里正常情况下都会成功。
                 * (release方法见AQS源码解析系列博客)
                 */
                if (release(savedState)) {
                    // 失败标记设置为false,不会再走下面finally中 失败并设置节点状态为取消的逻辑
                    failed = false;
                    /*
                     * 返回当前线程释放的state值
                	 * 为什么要返回savedState?
                     * 这里最终要返回加锁前的状态,因为执行到这时,构造出来的Node节点还在等待队列中,
                     * 当迁移到同步队列后,再次被唤醒,还需要进行获取锁释放锁的逻辑,所以这里需要记录下初始锁的状态并返回。
                     */
                    return savedState;
                } else {
                    throw new IllegalMonitorStateException();
                }
            } finally {
                if (failed)
                    node.waitStatus = Node.CANCELLED;
            }
        }

isOnSyncQueue()

        /*
         * 判断当前Node是否在同步队列中
         */
        final boolean isOnSyncQueue(Node node) {
            /*
             * 条件1:node.waitStatus == Node.Condition(-2)条件成立,
             * 说明当前Node一定在等待队列,因为signal方法迁移节点到同步队列前,会将node的状态设置为0
             *  
             * 条件2:前置条件:node.waitStatus != Node.Condition
             *   1.此时node.waitStatus 可能 = 0(表示当前节点已经被signal了 在不在阻塞队列另说 可能在迁移过程中)
             *   2.此时node.waitStatus 可能 = 1(取消) (当前线程是未持有锁调用await方法,最终会将node的状态改为取消状态)
             *  
             * node.waitStatuas == 0 为什么还要判断 node.prev == null?
             * 因为signal是先修改状态 再迁移
             * 因为等待队列的Node是单向链表,node.prev == null 说明一定是在等待队列中
             */
            if (node.waitStatus == Node.CONDITION || node.prev == null)
                // 直接return false
                return false;
            /*
             * 执行到这里,会是哪种情况?
             * node.waitStatus != Condition 且 node.prev != null 
             * 可以排除 node.waitStatus == 1 取消状态
             * 为什么可以排除?因为signal不会把取消状态的node迁移走
             *  
             * 设置prev引用的逻辑 是 迁移到同步队列设置的(enq()自旋入队)
             * 入队的逻辑:
             * 1.设置node.prev = tail; 
             * 2.CAS设置当前node为tail,成功才算真正进入到同步队列
             * 3.pred.next = node;
             * 可以推算出,就算prev不为null,也不能说明当前node已经成功入队到同步队列了(CAS成功才算)
             */

             /*
              * next不为null,说明当前节点已经成功入队到同步队列了,且当前节点后面已经有其他node了
              */
            if (node.next != null) 
                return true;
            /*
             * 执行到这里,说明当前节点的状态为 node.witStatus = 0 && node.prev != null
             * findNodeFromTail:从同步队列的尾巴开始向前遍历查找node,查找成功返回ture,失败返回false
             * 当前node有可能在signal过程中,正在迁移中..还未完成
             */
            return findNodeFromTail(node);
        }

	// 在阻塞队列中从后向前遍历查找元素
	private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

checkInterruptWhileWaiting()

    /*
     * 检查线程是在哪里被中断的 等待/同步(正在迁移过程中)
     */    
	private int checkInterruptWhileWaiting(Node node) {
            /*
             * Thread.interrupted():返回当前线程中断标记位,并且重置当前线程标记位为false
             * transferAfterCancelledWait(node):如果当前线程被中断过,才会执行该方法,
             * 被中断过就去判断是在哪里中被中断的,最终返回-1/1(-1表示在等待队列内,1表示在等待队列外(同步队列/迁移过程中))
             * 没有被中断过直接返回0
             */
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

transferAfterCancelledWait()

          /*
           * @return true  表示node是在等待队列中唤醒的
           *		 false 表示node不是在等待队列中唤醒的(同步队列/在迁移过程中)
           */
          final boolean transferAfterCancelledWait(Node node) {
            /*
             * 条件成立:说明当前的node一定是在等待队列中被中断唤醒的
             * CAS期望值为-2,修改为0,signal迁移节点到同步队列时,会将节点的状态修改为0
             */
            if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { 
                // 在等待队列中 被中断唤醒的node也会被迁移到同步队列中
                enq(node);
                // 返回 true 表示node是在等待队列中被中断的
                return true;
            }
            /*
             * 执行到这里有几种情况?
             * 1.当前node已经被外部线程调用signal 方法将其迁移到 同步队列了
             * 2.当前node正在被外部线程调用signal 方法将其迁移到同步队列 进行中..
             * (因为将node从等待队列迁移到同步队列时会先将状态改为0)
             */
            while (!isOnSyncQueue(node))
                // 如果当前线程状态被修改为0,但还未进入同步队列中,则短暂的释放CPU,再去抢占。
                Thread.yield();
            // 返回false这里表示node是在同步队列中被中断的(这里可能有些许歧义,可能此时还未进入同步队列,但该node一定不在等待队列中)
            return false;
        }

unlinkCancelledWaiters()

        /*
         * 将等待队列中的所有取消状态的node全部出队
         */
        private void unlinkCancelledWaiters() {
            // 循环当前节点,从链表的第一个节点开始向后迭代
            Node t = firstWaiter;
            // 当前链表上一个正常状态的node节点
            Node trail = null;
            // 遍历
            while (t != null) {
                Node next = t.nextWaiter;
                // 当前节点t状态不正常(为取消状态)
                if (t.waitStatus != Node.CONDITION) {
                    // 直接将nextWaiter置为null
                    t.nextWaiter = null;
                    // 条件成立:说明遍历到的节点还未碰到过任何正常节点
                    if (trail == null)
                        // 将firstWaiter指向next,即删除中间为取消状态的节点
                        firstWaiter = next;
                    // 执行到else这里,说明已经遇到过正常节点
                    else
                        // 让上一个正常节点指向 取消节点的 下一个节点,即跳过(删除)当前有问题的节点
                        trail.nextWaiter = next;
                    // 条件成立:遍历到链表的末尾了
                    if (next == null)
                        // 更新lastWaiter指向最后一个正常节点
                        lastWaiter = trail;
                }
                // 条件不成立执行到else,说明当前节点是正常节点
                else
                    trail = t; // 更新一下trail
                // 更新下一个节点
                t = next;
            }
        }

reportInterruptAfterWait()

    	/*
         * 根据interruptMode的值决定中断的处理方式
         * 1.interruptMode = THROW_IE(-1):在条件队列内发生过中断
         * 2.interruptMode = REINTERRUPT(1):在条件队列外发生过中断
         */
		private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            // 条件成立:说明在条件队列内发生过中断,此时await方法抛出中断异常
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            // 条件成立:说明在条件队列外发生的中断,此时设置当前线程的中断标记为为 true
            // 中断处理 交给业务代码处理,如果你不处理 什么事也不会发生..
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

signal() 唤醒后的逻辑

        public final void signal() {
            // 判断当前调用signal方法的线程是否是持有锁的线程
            if (!isHeldExclusively())
                // 如果不是 则直接抛异常
                throw new IllegalMonitorStateException();
            // 获取条件队列的第一个node
            Node first = firstWaiter;
            // 不为null,则将第一个节点进行迁移到同步队列的逻辑
            if (first != null)
                doSignal(first);
        }

doSignal()

   		private void doSignal(Node first) {
            // 自旋
            do {
                /*
                 * 当前节点要进行出队,所以更新firstWaiter 指向下一个结点
                 * 如果当前等待队列只有一个节点的话,需要更新lastWaiter为null
                 */
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 当前first节点出队,断开和下一个节点的关系
                first.nextWaiter = null;      
                /*
                 * 条件1:transferForSignal(first) true->迁移成功 false->迁移失败,取反后 迁移失败会进入条件2的逻辑:
                 * 条件2:(first = firstWaiter) != null 当前first迁移失败,则将first更新为 first.next 继续尝试迁移..
                 * 直至迁移某个节点成功,或者 条件队列为null为止。
                 * 这里表示要么迁移一个节点成功 要么 队列为空跳出循环。
                 */         
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

AQS.transferForSignal()

	/*
     * 真正迁移节点的逻辑
     * @return 返回true->表示迁移成功 返回false->表示迁移失败..
     */
	final boolean transferForSignal(Node node) {
        /*
         * CAS修改的当前Node的状态为0,因为马上node要迁移到同步队列了,
         * 成功:当前节点在条件队列中状态正常。
         * 失败:
         *   1.取消状态(线程await时 未持有锁,最终线程对应的node会设置为 取消状态)
         *   2.node对应的线程 挂起期间,被其它线程使用 中断信号 唤醒过...(就会主队进入到 阻塞队列,这时也会修改状态为0)
         * 失败返回false,尝试唤醒下一个节点
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        /*
         * enq() 自旋进入同步队列,一定会入队成功,最终返回当前node进入同步队列后的前驱节点
         * enq()方法参考AQS源码解析
         * p 就是当前节点在同步队列的的前驱节点
         */
        Node p = enq(node);
        // 获取前驱节点的waitStatus
        int ws = p.waitStatus;
        /*
         * 条件1:判断如果前驱节点的状态 > 0(取消),那么直接唤醒node。
         * 条件2:前置条件:前驱节点的node状态 <= 0,那么尝试CAS修改为-1(signal),即前驱节点释放锁后唤醒后继节点。
         * 设置失败说明前驱节点的状态突然被取消了,需要唤醒后继节点。
         * 当前驱node对应的线程是lockInterrupt入队时的node,是会响应中断的,
         * 外部线程给前驱node中断信号之后,前驱node会将状态修改为取消状态,并且执行出队逻辑。
         */
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            // 走到这里 说明前驱节点状态处于取消状态,那么直接唤醒当前node对应的线程。
            LockSupport.unpark(node.thread);
        return true;
    }

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小成同学_

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值