java并发锁ReentrantLock源码分析二之Condition实现原理

1、java.util.concurrent.locks.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();

Condition 实现的语义为 Object.wait 与 Object.notify。

关于Condition 的实现类为 AbstractQueuedSynchronizer.ConditionObject 内部类。

首先在讲解源码之前,我重点罗列出ConditionObject的关键数据结构:

private transient Node fristWaiter;

private transient Node lastWaiter;

从这里看出,每个CondtionObject,都维护着自己的条件等待等待队列,并且是一个双端链表。

1.1 void await() throws InterruptedException

/**
         * Implements interruptible condition wait.
         * <ol>
         * <li> If current thread is interrupted, throw InterruptedException.
         * <li> Save lock state returned by {@link #getState}.
         * <li> Invoke {@link #release} with
         *      saved state as argument, throwing
         *      IllegalMonitorStateException if it fails.
         * <li> Block until signalled or interrupted.
         * <li> Reacquire by invoking specialized version of
         *      {@link #acquire} with saved state as argument.
         * <li> If interrupted while blocked in step 4, throw InterruptedException.
         * </ol>
         */
        public final void await() throws InterruptedException {
            if (Thread.interrupted())    // @1 
                throw new InterruptedException();

            Node node = addConditionWaiter();    //@2
            int savedState = fullyRelease(node);   // @3
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {     //@4
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)  // @5
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  //@6
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled                                 //@7
                unlinkCancelledWaiters();
            if (interruptMode != 0)                                                                            //@8
                reportInterruptAfterWait(interruptMode);
        }

代码@1:检测当前线程的中断标记,如果中断位为1,则抛出异常。

代码@2:添加等待节点。就是一个简单的链表维护节点的操作,具体参照addConditionWaiter讲解。

代码@3:释放占有的锁,并获取当前锁的state,因为await实现的语意为Object.wait,释放锁并并等待条件的发生。当条件满足后,线程被唤醒后,第一步是需要获取锁,然后在上次await的下一条指令处继续执行。代码3就是实现上述语义的释放锁。

代码@4:isOnSyncQueue 当前节点是否在同步队列中,如果在同步阻塞队列中,则申请锁,去执行;如果不在同步队列中(在条件队列中),阻塞,等待满足条件,新增的节点,默认在条件队列中(Conditon)。isOnSyncQueue 源码解读在下文中;

代码@5:线程从条件等待被唤醒,唤醒后,线程要从条件队列移除,进入到同步等待队列,被唤醒有有如下两种情况,一是条件满足,收到singal信号,二是线程被取消(中断),该步骤是从条件队列移除,加入到同步等待队列,返回被唤醒的原因,如果是被中断,需要根据不同模式,处理中断。处理中断,也有两种方式:1.继续设置中断位;2:直接抛出InterruptedException。请看下文关于checkInterruptWhileWaiting的源码解读。

代码@6:运行到代码6时,说明线程已经结束了释放锁,从条件队列移除,线程运行,在继续执行业务逻辑之前,必须先获取锁。只有成功获取锁后,才会去判断线程的中断标志,才能在中断标志为真时,抛出InterruptException。

代码@7,执行一些收尾工作,清理整个条件队列:

代码@8,处理中断,是设置中断位,还是抛出InterruptException。

那我们先关注一下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) {     //@1
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);  //@2
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

添加条件等待节点,根据链表的特征,直接在尾部节点的nextWaiter指向新建的节点,并将新建的节点设置为整个链表的尾部,首先要知道如下数据结构:

    object {

          Node firstWaiter;

          Node lastWaiter;

          node {

               node nextWaiter;

              该节点承载的业务数据,比如这里的Thread t;等

         }

    }

知道上述结构,其实整个链的数据维护,基本一目了然,自己都可以实现下面的逻辑。

代码@1,如果最后一个等待节点的状态不是Node.CONDITION,则,则先删除等待链中节点状态不为Node.CONDITION的节点。具体代码分析请参照下文unlinkCancelledWaiters的解读。

代码@2开始,就是普通链表的节点添加的基本方法。

清除等待节点方法。

/**
         * 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;  // 
            Node trail = null;       //@1
            while (t != null) {
                Node next = t.nextWaiter;    
                if (t.waitStatus != Node.CONDITION) {  // @3
                    t.nextWaiter = null;
                    if (trail == null)                      // @4
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;       //@5
                    if (next == null)    // @6
                        lastWaiter = trail;
                }
                else   // @4
                    trail = t;
                t = next;
            }
        }

该方法的思路为,从第一节点开始,将不等于Node.CONDITION的节点。

代码@1,设置尾部节点临时变量,用来记录最终的尾部节点。代码@1 第一次循环,是循环第一个节点,如果它的状态为Node.CONDITION, 则该链的头节点保持不变,设置临时尾节点为t,然后进行一个节点的判断,如果节点不为Node.CONDITION, 重置头节点的下一个节点,或尾部节点的下一个节点(@4,@5)。代码@6代表整个循环结束,设置 ConditionObject对象的lastWaiter为trail的值;

await步骤中,释放锁过程源码解析。释放锁的过程,逻辑为unlock,但该方法,返回当前锁的state,因为释放锁后,该方法在条件没有满足前提下,自身需要阻塞。被唤醒后,需要先尝试获取锁,然后才能执行接下来的逻辑。

/**
     * Invokes release with current state value; returns saved state.
     * Cancels node and throws exception on failure.
     * @param node the condition node for this wait
     * @return previous sync state
     */
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

await,@4步骤中,isOnSyncQueue 源码解读:

/**
     * Returns true if a node, always one that was initially placed on
     * a condition queue, is now waiting to reacquire on sync queue.
     * @param node the node
     * @return true if is reacquiring
     */
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)   // @1
            return false;
        if (node.next != null) // If has successor, it must be on queue   // @2
            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);
    }

代码@1,如果节点的状态为Node.CONDITION 或 node.prev == null,表明该节点在条件队列中,并没有加入同步阻塞队列(同步阻塞队列为申请锁等待的队列),await方法中,新增的节点,默认满足上述条件,所以返回false,表示在条件队列中,等待条件的发生,条件满足之前,当前线程应该阻塞。这里,先预留一个疑问,那node.prev在什么时候会改变呢?

代码@2,如果node.next不为空,说明在同步阻塞队列中。这个我想毫无疑问。当然也说明next域肯定是在进入同步队列过程中会设置值。

代码@3, 上面的注释也说的比较清楚,node.prev不为空,但也不在同步队列中,这个是由于CAS可能会失败,为了不丢失信号,从同步队列中再次选择该节点,如果找到则返回true,否则返回false,在这里,我就更加对node.prev在什么时候会设置值感兴趣了,请继续await方法向下看,总有水落石出的时候。

await @5 checkInterruptWhileWaiting 代码解读:

 /*
         * For interruptible waits, we need to track whether to throw
         * InterruptedException, if interrupted while blocked on
         * condition, versus reinterrupt current thread, if
         * interrupted while blocked waiting to re-acquire.
         */

        /** Mode meaning to reinterrupt on exit from wait */
        private static final int REINTERRUPT =  1;   // 重新设置中断位,中断由上层处理
        /** Mode meaning to throw InterruptedException on exit from wait */  
        private static final int THROW_IE    = -1;    // 直接抛出 InterruptedException  0:正常

        /**
         * Checks for interrupt, returning THROW_IE if interrupted
         * before signalled, REINTERRUPT if after signalled, or
         * 0 if not interrupted.
         */
        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

/**
     * Transfers node, if necessary, to sync queue after a cancelled
     * wait. Returns true if thread was cancelled before being
     * signalled.
     * @param current the waiting thread
     * @param node its node
     * @return true if cancelled before the node was signalled
     */
    final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {   //@1
            enq(node);
            return true;
        }
        /*
         * 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();
        return false;
    }

/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) { 
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

重点关注@1,首先需要知道一点,如果收到正常的singal()信号而被唤醒的节点【这个再singal方法时重点分析】,状态为Node.SINGAL,不会是Node.CONDITION状态,所以如果代码1compareAndSetWaitStatus设置成功,说明线程是调用了t.interrupt方法而使得LockSupport.park解除阻塞的,然后将该节点加入到同步队列中,使得 while( ! isOnSyncQueue(node)) 的条件为真,结束 await的等待条件触发语义,,进入到 抢占锁阶段。【再次重申Object wait语义,释放当前锁,然后等待条件的触发【条件队列】,,条件发生后,要先重新去抢占锁,获取锁则继续执行,否则阻塞在获取锁【同步队列】】,所以当 线程阻塞在  await 方法时,调用 t.interrupt方法时只是中断条件队列的等待,并不能马上取消执行,马上抛出InterrupterException。

await方法流程图:


1.2 signal()方法详解 

/**
         * Moves the longest-waiting thread, if one exists, from the
         * wait queue for this condition to the wait queue for the
         * owning lock.
         *
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signal() {  // @1
            if (!isHeldExclusively())  //如果当前线程不是锁的持有者,直接抛出异常。
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first); //通知第一个等待者          //@2
        }

        /**
         * Removes and transfers nodes until hit non-cancelled one or
         * null. Split out from signal in part to encourage compilers
         * to inline the case of no waiters.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {  
            do {
                if ( (firstWaiter = first.nextWaiter) == null)   // @3
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);                          // @4
        }

    /**
     * Transfers a node from a condition queue onto sync queue.
     * Returns true if successful.
     * @param node the node
     * @return true if successfully transferred (else the node was
     * cancelled before signal).
     */
    final boolean transferForSignal(Node node) {  // @5
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */ 
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))   //@6
            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);     // @7
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))     //@8
            LockSupport.unpark(node.thread);
        return true;
    }

signal的具体实现,是从doSignal方法开始。

@代码3,首先将要被通知的节点的下一个节点设置为等待队列的head节点,如果当前节点的下一个节点为空,则设在等待队列的尾节点(lastWaiter)设置为空,然后将当前被通知的节点的下一个节点设为空;该步骤核心思想就是将被通知节点移除条件等待队列,然后重新维护条件等待对的firstWaiter和lastWaiter。

@代码4,!transferForSignal(first) && (first = firstWaiter) != null,根据后文的解析可以知道,如果被通知节点没有进入到同步阻塞队列(transferForSignal返回false)并且条件等待队列还有不为空的节点,则继续循环通知。

@代码5,transferForSignal该方法,将被通知的节点放入同步等待队列。

@代码6,首先判断,尝试将节点状态设置为0,如果设置失败,则说明该节点的状态已经不是Node.CONDITION,进一步说明该节点在没有等到通知信号时,被取消,直接返回false,通知下一个等待者。(回到代码@3,@4)

@代码7,将节点放入到同步队列中。个人认为信号通知,主要是将节点从条件等待队列移入到同步等待队列,主要是防止sinal信号的丢失。

@代码8,如果前置节点取消,或者在设置前置节点状态为Node.SIGNAL状态失败时,唤醒被通知节点代表的线程,@8设置失败发送的情况也就是前置节点状态发送改变(被取消等),所以直接唤醒被通知节点的线程,也就是说,sinal方法,只有在入队列后,前置节点被取消时,才会执行LockSupport.unpark方法唤醒线程,通常该方法,只是将节点从条件等待队列放入同步队列,然后该方法执行完毕,释放持有的锁。

整个通知sinal方法的流程如下:

欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:

1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏
9、源码分析Mycat专栏

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

中间件兴趣圈

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

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

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

打赏作者

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

抵扣说明:

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

余额充值