多线程 | AQS | 条件队列 Condition 源码分析

1)条件队列概念:

条件队列是当某个条件不满足状态时,挂起自己并释放锁,一旦等待条件为真,则立即醒来。这也是条件队列提供的主要功能。
Object的wait/notify/notifyAll等方法构成了内部条件队列的API,在lock中又是怎么实现的呢?
就是这里要讨论的条件队列 condition。

具体使用示例就不展示了,和 wait/notify/notifyAll 使用场景类似,接下来直接看源码。

2)源码分析:

condition一般会结合lock使用,获取方式如下:

    ReentrantLock reentrantLock = new ReentrantLock();
    Condition condition = reentrantLock.newCondition();

通过内部类 sync 获取condition,因为多个线程共享一个lock,所以这里的conditionObject也是多个线程共享。

    final ConditionObject newCondition() {
            return new ConditionObject();
        }

先看一张流程图:
这里写图片描述

接下来具体分析上面的几个核心方法:

public final void await() throws InterruptedException {
            //响应中断锁
            if (Thread.interrupted())
                throw new InterruptedException();
            //在此Condition维护的等待队列中增加节点
            Node node = addConditionWaiter();
            //挂起线程之前,必须释放当前锁,这里调用 reentrantLock的释放逻辑实现。
            int savedState = fullyRelease(node);

            int interruptMode = 0;
            //判断当前的线程是否在同步队列中,如果不在当前队列中,直接挂起当前线程,等待被 notify 唤醒。
            while (!isOnSyncQueue(node)) {
                //如果当前的node的WaitStatus是Condition,那么park此线程
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //当该线程被 notify唤醒之后,调用独占锁的逻辑去抢锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }



接下来一步步看:
首先是增加线程节点逻辑,每次增加线程节点的时候都会剔除状态非condition的节点。

       private Node addConditionWaiter() {
            Node t = lastWaiter;
            //t为最后一个等待节点,如果不为空,则表示有线程在等待队列排队
            if (t != null && t.waitStatus != Node.CONDITION) {
            //如果当前节点的状态不是 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;
        }



下面是判断当前线程是否在同步队列中:

final boolean isOnSyncQueue(Node node) {
        //如果当前线程为condition状态,或者prev为null(代表者等待队列为空,那同步队列肯定也为空,所以当前节点不是同步队列节点,直接进入等待队列)
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        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.
         */
        return findNodeFromTail(node);
    }

如果这里返回了true,那么表示已经在队列中,那么继续争抢锁。
这种主要场景是已经释放锁,如果返回true,证明当前线程已经被唤醒(已经执行过signal方法)。如果返回false,证明没有singal,所以要挂起当前的线程。

这样await的逻辑就清晰了,接下来是一个signal的过程,调用signal的时候:

public final void signal() {
            //检查当前线程是不是,Lock占用的线程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //取出Condition队列中第一个Node
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

在唤醒方法 doSignal 中会执行以下逻辑:

        final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        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).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        //这里设置节点的状态为 Node.SIGNAL,这里如果设置不成功,也会挂起线程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

上面设置节点的状态为 Node.SIGNAL,这里如果设置不成功,也会挂起线程,等待持有锁的线程释放锁后,唤起当前节点。

扫描二维码关注公众号,回复: 2603700 查看本文章

猜你喜欢

转载自blog.csdn.net/woshilijiuyi/article/details/79313247