Java并发之同步三:Condition条件队列

发布时间:2024年01月12日

一、总览

在这里插入图片描述

二、源码分析

2.1 人口

  public Condition newCondition() {
        return sync.newCondition();
    }

 final ConditionObject newCondition() {
            return new ConditionObject();
   }
 public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        // 指向条件队列的第一个node节点
        private transient Node firstWaiter;
        // 指向条件队列的最后一个node节点
        private transient Node lastWaiter;

        /**
         * Creates a new {@code ConditionObject} instance.
         */
        public ConditionObject() { }
 }

await方法:

    public final void await() throws InterruptedException {
            // 判断当前线程是否是中断
            if (Thread.interrupted())
                throw new InterruptedException();
            // 将调用await方法的线程包装成node并加入条件队列中,并返回当前node
            Node node = addConditionWaiter();
            // 完全释放当前线程对应的锁,
            // 为什么要释放锁那?加着锁挂起后,谁还能救你那
            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、常规路径:外部线程获取到锁之后,调用signal()方法 转移条件队列的头节点到阻塞队列,当
                //   这个节点获取到锁后,会唤醒
                
                // 2.转移至阻塞至阻塞队列后,发现阻塞队列中的前驱节点状态是 取消状态,此时会唤醒当前节点
                // 3.当前节点挂起期间时,被外部线程使用中断唤醒
                
                // checkInterruptWhileWaiting: 就算在condition队列挂起期间,线程发生中断了,
                // 对应的node也会被迁移到“阻塞队列”
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // acquiredQueued:竞争队列的逻辑
            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)
                // 条件队列内发生中断,此外await方法抛出中断异常
                // 条件队列外发生的中断,交给你的业务处理,如果你不处理,那什么事也不会发生
                reportInterruptAfterWait(interruptMode);
        }
  1. 将当前线程包装成ConditionNode加入到条件队列中
  2. 释放锁资源
  3. while循环,如果当前线程对应的node已经迁移到“阻塞队列”,那就park挂起它,然后开始走acquireQueued逻辑,开始竞争锁 (这里线程什么时候会到“阻塞队列”中那)
private int checkInterruptWhileWaiting(Node node) {
            // thread.interrupted 返回当前线程中断标记位,并且重置当前标记为false
            return Thread.interrupted() ?
                // transferAfterCancel 这个方法只有在线程是被中断唤醒时才会调用
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

 final boolean transferAfterCancelledWait(Node node) {
        // 条件成立:说明当前node一定是在条件队列内,因为signal迁移节点
        // 到阻塞队列时,会将节点的状态修改为0
        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
            // 中断唤醒的node也会被加入到阻塞队列中
            enq(node);
            // true:表示是在条件队列内被中断的
            return true;
        }
       
        // 执行到这里有几种情况?
        // 1、当前node已经被外部线程调用 signal方法将其迁移到阻塞队列内
        // 2、当前node正在被外部线程调用 signal方法将其迁移到阻塞队列中
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

signal方法

 public final void signal() {
            // 判断调用signal方法的线程是否是独占锁持有线程,如果不是,直接抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
    private void doSignal(Node first) {
            do {
                // firstWaiter = first.nextWaiter 因为当前first马上要出条件队列
                // 如果当前节点的下一个节点是NULL,说明条件队列只有当前一个节点了。。
                // 当前出队后,整个队列就空了
                // 所以需要更新lastWaiter = null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 当前first节点 出条件队列,断开和下一个节点的关系
                first.nextWaiter = null;
                
                // transferForSignal(first)
                // true:当前first节点迁移到阻塞队列成功 false迁移失败
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
  final boolean transferForSignal(Node node) {
        // cas 修改当前节点的状态,修改为0,因为当前节点马上要迁移到 阻塞队列中
        // 成功:当前节点在条件队列中状态正常
        // 失败: 1、取消状态(线程await时,未持有锁,最终线程对应的node会设置为取消状态)
        //       2、node对应的线程挂起期间,被其他线程使用中断信号 
                   唤醒过(就会主动进入到阻塞队列中),这时候也会修改状态为0
        if (!node.compareAndSetWaitStatus(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).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        // 条件一:ws >0 成立:说明前驱节点状态在阻塞队列中是取消状态,唤醒当前节点
        // 条件二: 前置条件(ws <= 0) 
        //  compareAndSetWaitStatus(p,ws,Node.SIGNAL)表示设置前驱节点状态 SIGANAL状态成功
        //  compareAndSetWaitStatus(p,ws,NOde.SIGNAL)返回false ==> 什么时候返回false
        //  当前驱node对应的线程是lockInterrupt 入队的node时,是会响应中断的,外部线程给前驱线程中断
        //  信号之后,前驱node状态修改为取消状态,并且执行出队逻辑
        //  前驱节点状态只要不是0 或者-1,那么就唤醒当前线程
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
文章来源:https://blog.csdn.net/LittleStar_Cao/article/details/135541277
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。