AQS中Condition接口实现的await方法的简单分析

最近,重读源码,想着做个笔记。这次重点读的是await方法的源码。

 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);// 挂起当前线程,被唤醒后,将从这里继续往下运行
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;//如果是因为中断被唤醒,则直接break
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

 

1.将当前线程封装成Node添加到条件队列(addConditionWaiter方法)
 2.完全释放当前线程所占用的锁(由state值变为0,对于可重入锁释放之前获取的全部锁次数),保存当前的锁状态(fullyRelease方法)
 3.自旋判断: 如果当前node(当前线程被封装成一个节点)不在同步队列中,则直接将当前线程挂起(isOnSyncQueue判断是否在同步队列里面,如果不在同步队列里面则会执行下面的park方法)。这里用自旋操作,如果是执行singal方法唤醒的,会将节点加入到sync队列中,到时侯线程被唤醒后,跳出自旋。然后是执行acquireQueued方法。
 4.线程被唤醒后,判断是何种方式唤醒的,如果是因为中断,则跳出循环,执行的break。 

这里主要分析下方法isOnSyncQueue:

 final boolean isOnSyncQueue(Node node) {
        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);
    }

    /**
     * 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;
                }
            }
        }
    }

判断在不在同步队列中,需要我们结合enq方法(将节点加入到同步队列中)来看

第一个if的判断:节点状态为 codeition,说明是在条件队列中,没有前节点说明不在同步队列中(sync队列是双向的)

第二个if条件:如果有后续节点(node的next属性有值那么它一定在sync队列中),说明当前节点一定在同步队列中。

为啥还会用到findNodeFromTail方法判断是否在同步队列中,就需要看enq方法了。

enq将一个新节点的入队分了三步:

  1. 设置node的前驱节点为当前的尾节点:node.prev = t
  2. 修改tail属性,使它指向当前节点
  3. 修改原来的尾节点,使它的next指向当前节点

所以prev不为空时,我们无法判断节点到底在不在sync中。因为cas操作可能失败,那么这个结点还没有在sync中,如果cas成功,那么它在sync中。但是我们这个方法判断节点是否在sync中,为啥是采用从尾节点开始逆向遍历链表。是因为从前往后遍历是不准的,prev有值的节点不一定在sync中,还有就是节点的next属性无值时,实际上新节点已经是tail了(对应的就是第2步成功,第三步还没有执行)。如果我们逆向遍历,只要tail(volatile修饰的)节点的prev指向这个节点(第2步发生了,则第1步一定发生),那么这个节点一定在sync中。

最后,findNodeFromTail方法   从尾节点开始遍历,判断是否在sync队列中。(对应节点已经加入进去同步队列了,但是最后设置next属性还没执行的情况)

 private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

线程被挂起后,如果要被唤醒,则有俩种方式:被中断,signal方法唤醒当前线程。checkInterruptWhileWaiting方法进行判断中断的方式。0代表未被中断(即通过signal唤醒的),THROW_IE代表在signal之前被中断了,REINTERRUPT代表是中断发生在signal之后。

 /**
         * 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;
        }

一共三种被唤醒的情况,现在分析最简单的情况,同时也是大多数情况正常的signal:

1)唤醒后,回到 while (!isOnSyncQueue(node))这一步,这时候,节点在同步队列里面(被signal唤醒则绝对在sync队列中<查看signal的源码得出的结论>,由于已经发生过signal了,则此时node必然已经在sync queue中),所以isOnSyncQueue将返回true,我们将退出while循环。接下来执行的就是

 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;

acquireQueued方法是唤醒后,去抢锁的逻辑,这个过程如果发生了中断则返回true,则修改interruptMode为REINTERRUPT。否则中断状态为0,之后不会抛出中断异常。

 if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();

将已经取消的节点,取消掉。

if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);

如果发生了中断,则抛出一个中断的异常。

2)中断发生时,线程还没有被signal过 ,对应状态:THROW_IE

       /**
         * 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;
        }



       final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            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;

方法transferAfterCancelledWait用于判断是何种中断导致唤醒的,现在我们分析返回true的情况,也就是THROW_IE的中断状态。待会我们分析另一种中断情况。

判断一个node是否被signal过,一个简单有效的方法就是判断它是否离开了condition queue, 进入到sync queue中。

换句话说,只要一个节点的waitStatus还是Node.CONDITION(await方法第一步加入条件队列,这时候状态就是Node.CONDITION),那就说明它还没有被signal过。

        /**
         * 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) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

这时候,执行CAS操作(compareAndSetWaitStatus(node, Node.CONDITION, 0)),状态改为0。

接下来执行的就是,将这个node加入到同步队列中(enq(node)),然后返回true(说明是THROW_IE的中断状态),这时候,while循环,执行break。接下来,第一个if条件((acquireQueued(node, savedState)争夺锁的方法)会直接跳过,第二个if条件(if (node.nextWaiter != null))判断是否还有后节点(条件队列),有则断开后节点的连接。接下来,抛出中断异常(查看reportInterruptAfterWait方法代码)。

3)中断发生时,已经发生过了signal的情况。对应中断状态:REINTERRUPT

分了俩种情况:

3.1)被唤醒时,已经发生了中断,但此时线程已经被signal过了。对应状态:REINTERRUPT

回到之前第二种情况的判断逻辑处:transferAfterCancelledWait方法,while循环判断当前节点是否在同步队列中,如果在,直接跳出循环,然后返回fasle,对应REINTERRUPT状态。如果,还不在同步队列中(处于从条件队列移除后在去往同步队列的路上)。这时候会先挂起,直到下次被唤醒,然后继续执行while处逻辑,判断是否在同步队列中。然后执行break,剩下的分析和前面一样。最后,执行reportInterruptAfterWait方法,这里并没有抛出中断异常,而只是将当前线程再中断一次。

3.2)被唤醒时,并没有发生中断,但是在抢锁的过程中发生了中断。对应状态:REINTERRUPT

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)这个条件返回是0,但是在接下来的acquireQueued(node, savedState)的抢夺锁时,发生了中断,最后执行reportInterruptAfterWait方法,这里并没有抛出中断异常,而只是将当前线程再中断一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值