AQS_Condition——await,signal

本文深入剖析了Java并发库中的AbstractQueuedSynchronizer(AQS)内部实现,重点讨论了条件队列的工作原理,包括节点状态转换、线程等待与唤醒机制。详细解释了`await()`、`signal()`方法的内部逻辑,如线程中断处理、节点迁移以及队列管理。通过对源码的分析,揭示了AQS在并发控制中的关键细节。
摘要由CSDN通过智能技术生成

最基础的属性

        //指向条件队列的第一个node节点
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        //指向条件队列的最后一个node节点
        private transient Node lastWaiter;

await

        public final void await() throws InterruptedException {
            //判断当前线程是否是中断状态,如果是则直接给个中断异常了..
            if (Thread.interrupted())
                throw new InterruptedException();

            //将调用await方法的线程包装成为node并且加入到条件队列中,并返回当前线程的node。
            Node node = addConditionWaiter();
            //完全释放掉当前线程对应的锁(将state置为0)
            //为什么要释放锁呢?  加着锁 挂起后,谁还能救你呢?
            int savedState = fullyRelease(node);
            //0 在condition队列挂起期间未接收过过中断信号
            //-1 在condition队列挂起期间接收到中断信号了
            //1 在condition队列挂起期间为未接收到中断信号,但是迁移到“阻塞队列”之后 接收过中断信号。
            int interruptMode = 0;

            //isOnSyncQueue 返回true 表示当前线程对应的node已经迁移到 “阻塞队列” 了
            //返回false 说明当前node仍然还在 条件队列中,需要继续park!
            while (!isOnSyncQueue(node)) {
                //挂起当前node对应的线程。  接下来去看signal过程...
                LockSupport.park(this);
                //什么时候会被唤醒?都有几种情况呢?
                //1.常规路径:外部线程获取到lock之后,调用了 signal()方法 转移条件队列的头节点到 阻塞队列, 当这个节点获取到锁后,会唤醒。
                //2.转移至阻塞队列后,发现阻塞队列中的前驱节点状态 是 取消状态,此时会唤醒当前节点
                //3.当前节点挂起期间,被外部线程使用中断唤醒..


                //checkInterruptWhileWaiting :就算在condition队列挂起期间 线程发生中断了,对应的node也会被迁移到 “阻塞队列”。
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

            //执行到这里,就说明 当前node已经迁移到 “阻塞队列”了


            //acquireQueued :竞争队列的逻辑..
            //条件一:返回true 表示在阻塞队列中 被外部线程中断唤醒过..
            //条件二:interruptMode != THROW_IE 成立,说明当前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);
        }
        /**
         * Adds a new waiter to wait queue.
         * @return its new wait node
         * 调用await方法的线程 都是 持锁状态的,也就是说 addConditionWaiter 这里不存在并发!
         */        
		private Node addConditionWaiter() {
            //获取当前条件队列的尾节点的引用 保存到局部变量 t中
            Node t = lastWaiter;

            //条件一:t != null 成立:说明当前条件队列中,已经有node元素了..
            //条件二:node 在 条件队列中时,它的状态是 CONDITION(-2)
            //     t.waitStatus != Node.CONDITION 成立:说明当前node发生中断了..
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                //清理条件队列中所有取消状态的节点
                unlinkCancelledWaiters();
                //更新局部变量t 为最新队尾引用,因为上面unlinkCancelledWaiters可能会更改lastWaiter引用。
                t = lastWaiter;
            }

            //为当前线程创建node节点,设置状态为 CONDITION(-2)
            Node node = new Node(Thread.currentThread(), Node.CONDITION);

            //条件成立:说明条件队列中没有任何元素,当前线程是第一个进入队列的元素。
            //让firstWaiter 指向当前node
            if (t == null)
                firstWaiter = node;
            else//说明当前条件队列已经有其它node了 ,做追加操作
                t.nextWaiter = node;


            //更新队尾引用指向 当前node。
            lastWaiter = node;
            //返回当前线程的node
            return node;
        }
   final int fullyRelease(Node node) {
        //完全释放锁是否成功,当failed失败时,说明当前线程是未持有锁调用 await方法的线程..(错误写法..)
        //假设失败,在finally代码块中 会将刚刚加入到 条件队列的 当前线程对应的node状态 修改为 取消状态
        //后继线程就会将 取消状态的 节点 给清理出去了..
        boolean failed = true;
        try {
            //获取当前线程 所持有的 state值 总数!
            int savedState = getState();

            //绝大部分情况下:release 这里会返回true。
            if (release(savedState)) {
                //失败标记设置为false
                failed = false;
                //返回当前线程释放的state值
                //为什么要返回savedState?
                //因为在当你被迁移到“阻塞队列”后,再次被唤醒,且当前node在阻塞队列中是head.next 而且
                //当前lock状态是state == 0 的情况下,当前node可以获取到锁,此时需要将state 设置为savedState.
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

final boolean isOnSyncQueue(Node node) {
    //条件一:node.waitStatus == Node.CONDITION 条件成立,说明当前node一定是在条件队列,因为signal方法迁移节点到 阻塞队列前,会将node的状态设置为 0
    //条件二:前置条件:node.waitStatus != Node.CONDITION   ===>
    // 1.node.waitStatus == 0 (表示当前节点已经被signal了)
    // 2.node.waitStatus == 1 (当前线程是未持有锁调用await方法..最终会将node的状态修改为 取消状态..)
    //node.waitStatus == 0 为什么还要判断 node.prev == null?
    //因为signal方法 是先修改状态,再迁移。
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        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 已经成功入队到 阻塞队列了。


    //条件成立:说明当前节点已经成功入队到阻塞队列,且当前节点后面已经有其它node了...
    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.
     */
    /**
     * 执行到这里,说明当前节点的状态为:node.prev != null 且 node.waitStatus == 0
     * findNodeFromTail 从阻塞队列的尾巴开始向前遍历查找node,如果查找到 返回true,查找不到返回false
     * 当前node有可能正在signal过程中,正在迁移中...还未完成...
     */
    return findNodeFromTail(node);
}
private int checkInterruptWhileWaiting(Node node) {
    //Thread.interrupted() 返回当前线程中断标记位,并且重置当前标记位 为 false 。
    return Thread.interrupted() ?
            //transferAfterCancelledWait 这个方法只有在线程是被中断唤醒时 才会调用!
            (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
            0;
}

​ ->

final boolean transferAfterCancelledWait(Node node) {
    //条件成立:说明当前node一定是在 条件队列内,因为signal 迁移节点到阻塞队列时,会将节点的状态修改为0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        //中断唤醒的node也会被加入到 阻塞队列中!!
        enq(node);
        //true:表示是在条件队列内被中断的.
        return true;
    }

    //执行到这里有几种情况?
    //1.当前node已经被外部线程调用 signal 方法将其迁移到 阻塞队列内了。
    //2.当前node正在被外部线程调用 signal 方法将其迁移至 阻塞队列中 进行中状态..

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

    //false:表示当前节点被中断唤醒时 不在 条件队列了..
    return false;
}
        private void unlinkCancelledWaiters() {
            //表示循环当前节点,从链表的第一个节点开始 向后迭代处理.
            Node t = firstWaiter;
            //当前链表上一个正常状态的node节点
            Node trail = null;

            while (t != null) {
                //当前节点的下一个节点.
                Node next = t.nextWaiter;
                //条件成立:说明当前节点状态为 取消状态
                if (t.waitStatus != Node.CONDITION) {
                    //更新nextWaiter为null
                    t.nextWaiter = null;
                    //条件成立:说明遍历到的节点还未碰到过正常节点..
                    if (trail == null)
                        //更新firstWaiter指针为下个节点就可以
                        firstWaiter = next;
                    else
                        //让上一个正常节点指向 取消节点的 下一个节点..中间有问题的节点 被跳过去了..
                        trail.nextWaiter = next;

                    //条件成立:当前节点为队尾节点了,更新lastWaiter 指向最后一个正常节点 就Ok了
                    if (next == null)
                        lastWaiter = trail;
                }
                else//条件不成立执行到else,说明当前节点是正常节点
                    trail = t;

                t = next;
            }
        }

signal

public final void signal() {
    //判断调用signal方法的线程是否是独占锁持有线程,如果不是,直接抛出异常..
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();


    //获取条件队列的一个node
    Node first = firstWaiter;
    //第一个节点不为null,则将第一个节点 进行迁移到 阻塞队列的逻辑..
    if (first != null)
        doSignal(first);



}
        private void doSignal(Node first) {
            do {
                //firstWaiter = first.nextWaiter 因为当前first马上要出条件队列了,
                //所以更新firstWaiter为 当前节点的下一个节点..
                //如果当前节点的下一个节点 是 null,说明条件队列只有当前一个节点了...当前出队后,整个队列就空了..
                //所以需要更新lastWaiter = null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;


                //当前first节点 出 条件队列。断开和下一个节点的关系.
                first.nextWaiter = null;


                //transferForSignal(first)
                //boolean:true 当前first节点迁移到阻塞队列成功  false 迁移失败...
                //while循环 :(first = firstWaiter) != null  当前first迁移失败,则将first更新为 first.next 继续尝试迁移..
                //直至迁移某个节点成功,或者 条件队列为null为止。
            } while (!transferForSignal(first) &&
                    (first = firstWaiter) != null);
        }
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        //cas修改当前节点的状态,修改为0,因为当前节点马上要迁移到 阻塞队列了
        //成功:当前节点在条件队列中状态正常。
        //失败:1.取消状态 (线程await时 未持有锁,最终线程对应的node会设置为 取消状态)
        //     2.node对应的线程 挂起期间,被其它线程使用 中断信号 唤醒过...(就会主队进入到 阻塞队列,这时也会修改状态为0)
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            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).
         */
        //enq最终会将当前 node 入队到 阻塞队列,p 是当前节点在阻塞队列的 前驱节点.
        Node p = enq(node);

        //ws 前驱节点的状态..
        int ws = p.waitStatus;
        //条件一:ws > 0 成立:说明前驱节点的状态在阻塞队列中是 取消状态,唤醒当前节点。
        //条件二:前置条件(ws <= 0),
        //compareAndSetWaitStatus(p, ws, Node.SIGNAL) 返回true 表示设置前驱节点状态为 SIGNAl状态成功
        //compareAndSetWaitStatus(p, ws, Node.SIGNAL) 返回false  ===> 什么时候会false?
        //当前驱node对应的线程 是 lockInterrupt入队的node时,是会响应中断的,外部线程给前驱线程中断信号之后,前驱node会将
        //状态修改为 取消状态,并且执行 出队逻辑..
        //前驱节点状态 只要不是 0 或者 -1 那么,就唤醒当前线程。
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //唤醒当前node对应的线程...回头再说。
            LockSupport.unpark(node.thread);

        return true;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值