AQS(AbstractQueuedSynchronizer)源码解析(ConditionObject)

阅读须知

  • JDK版本:1.8
  • 文章中使用/**/注释的方法会做深入分析

正文

我们之前分析了AQS独占锁和共享锁的源码实现,接下来我们来分析AQS的ConditionObject,我们常用的ReentrantLock的Condition、ReentrantReadWriteLock的Condition等都是基于AQS的ConditionObject实现,我们首先来看ConditionObject类的成员变量:
AbstractQueuedSynchronizer.ConditionObject:

//condition队列的第一个节点
private transient Node firstWaiter;
//condition队列的最后一个节点
private transient Node lastWaiter;

关于节点Node的介绍我们在AQS(AbstractQueuedSynchronizer)源码解析(独占锁部分)文章中已经进行过详细说明,这里不再重复。下面我来看可以响应中断的条件等待实现:
AbstractQueuedSynchronizer.ConditionObject:

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    /*为等待队列添加一个新的节点*/
    Node node = addConditionWaiter();
    /*用当前state值调用release方法;返回保存的状态。取消节点并在失败时抛出异常*/
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    /*置于条件队列上的节点如果正在在同步队列上等待重新获取锁,则返回true*/
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this); //阻塞当前线程
        /*取消等待后检查中断,并返回对应的中断模式*/
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //为队列中的线程获取独占不中断模式,如果在等待时中断并且中断模式不是THROW_IE,则将中断模式设置为REINTERRUPT
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        /*断开条件队列中已经为取消状态的等待节点*/
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        /*根据中断模式做不同的操作*/
        reportInterruptAfterWait(interruptMode);
}

我们来描述一下这个方法的流程:

  1. 如果当前线程中断,则抛出InterruptedException。
  2. 保存getState方法返回的锁定状态。
  3. 以保存的状态作为参数调用release方法,如果失败则抛出IllegalMonitorStateException。
  4. 阻塞直到被唤醒或中断。
  5. 通过以保存的状态作为参数调用acquire方法来重新获取锁。
  6. 如果在步骤4中阻塞时中断,则抛出InterruptedException。

方法中提到了一个中断模式的概念,初始值为0,在后面的流程中可能变为THROW_IE或REINTERRUPT,我们来解释一下这两个常量:
AbstractQueuedSynchronizer.ConditionObject:

//此模式意味着重新中断退出等待
private static final int REINTERRUPT =  1;
//此模式意味着抛出InterruptedException退出等待
private static final int THROW_IE    = -1;

下面我们来逐一的分析一下每个步骤:
AbstractQueuedSynchronizer.ConditionObject:

private Node addConditionWaiter() {
    Node t = lastWaiter;
    //如果条件队列的最后一个节点已经是取消状态,则断开条件队列中已经为取消状态的等待节点
    if (t != null && t.waitStatus != Node.CONDITION) {
        /*断开条件队列中已经为取消状态的等待节点*/
        unlinkCancelledWaiters();
        t = lastWaiter; //重新取出lastWaiter
    }
    //为当前线程创建等待节点,等待状态为CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        //如果到这里条件队列的最后一个节点为null,说明队列中已经没有任何节点,则将当前节点作为条件队列的第一个节点
        firstWaiter = node;
    else
        //如果条件队列中还存在等待节点,则将当前节点作为当前最后一个等待节点的下一个节点
        t.nextWaiter = node;
    lastWaiter = node; //无论条件队列中是否还有等待节点,到这里都要将当前节点置为条件队列中的最后一个等待节点
    return node;
}

这里提到的节点状态在AQS独占锁源码分析的文章中进行过详细的说明,不清楚的读者可以前往查阅了解。
AbstractQueuedSynchronizer.ConditionObject:

private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null; //表示下一个遗留下来的状态为CONDITION的节点
    while (t != null) {
        //从条件队列的第一个节点向后遍历
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            //如果等待节点为取消状态,则将等待节点的下一个节点置为null,断开联系
            t.nextWaiter = null;
            if (trail == null)
                //如果没有遗留下来的节点,则将下一个节点置为条件队列的第一个节点
                firstWaiter = next;
            else
                //如果有遗留下来的节点,则将下一个节点置为遗留下来的节点的下一个节点
                trail.nextWaiter = next;
            if (next == null)
                //如果next为null,说明已经遍历到队列的末尾,则将队列的最后一个节点指向当前遗留下来的节点
                lastWaiter = trail;
        }
        else
            trail = t; //记录遗留下来的状态为CONDITION的节点
        t = next; //下一个节点继续循环
    }
}

这个方法主要完成两件事,第一是将所有取消状态的节点断开联系,第二是将所有遗留下来的CONDITION状态的节点连接起来,方法中trail变量的作用就是完成第二件事。

继续分析await方法中的步骤:
AbstractQueuedSynchronizer.ConditionObject:

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

释放锁的操作我们在AQS独占锁源码分析的文章中进行过详细分析,这里同样是以独占模式释放锁。
AbstractQueuedSynchronizer.ConditionObject:

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        //如果节点的等待状态为CONDITION或者没有前任节点,证明节点没有再同步队列上等待获得锁
        return false;
    if (node.next != null)
        //如果节点有后继节点,证明它一定在同步队列上等待获得锁
        return true;
    //从队尾节点向前搜索判断当前节点是否处于同步队列
    return findNodeFromTail(node);
}

这里从队尾节点向前搜索的原因是可能存在当前节点的前任节点非空但并未放在队列中,因为CAS放置在队列上可能会失败。所以我们必须从尾部遍历,以确保它真正实现它。在findNodeFromTail方法的调用中,节点总是靠近尾部,除非CAS失败(不太可能),所以我们基本上没有经过很多遍历。
AbstractQueuedSynchronizer.ConditionObject:

private int checkInterruptWhileWaiting(Node node) {
    /*如果当前线程已中断,根据中断的时机返回不同的中断模式*/
    //如果当前线程未中断,返回0
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

AbstractQueuedSynchronizer.ConditionObject:

final boolean transferAfterCancelledWait(Node node) {
    //CAS修改节点等待状态
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node); //CAS成功将节点转换进入同步队列
        return true;
    }
    //CAS操作失败,证明enq方法正在执行,在这里循环让出CPU直到enq方法完成,也就是节点已经进入同步队列
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

enq方法我们在AQS独占锁源码分析的文章中同样分析过。await方法的下一个步骤是尝试以独占不中断模式为唤醒的节点重新获取锁,也就是acquireQueued方法的内容,我们同样在AQS独占锁源码分析的文章中分析过,如果再次获取锁失败,会再次阻塞。接下来会断开条件队列中已经为取消状态的等待节点,这个流程上文我们已经分析过。下面我们来看最后一个步骤,根据中断模式来做不同的操作:
AbstractQueuedSynchronizer.ConditionObject:

private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    //上文介绍了THROW_IE、REINTERRUPT这两个常量的含义,操作就是在这里实现
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

到这里,await方法的流程就分析完成了。之前我们提到await方法是可以响应中断的,ConditionObject同样提供了不响应中断的awaitUninterruptibly方法,它的流程与await方法非常相似,这里不过多赘述,下面我们来看一下可以指定等待时间的await方法的另一个重载的版本:
AbstractQueuedSynchronizer.ConditionObject:

public final boolean await(long time, TimeUnit unit)
    throws InterruptedException {
    long nanosTimeout = unit.toNanos(time);
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter(); //为等待队列添加一个新的节点
    //用当前state值调用release方法;返回保存的状态。取消节点并在失败时抛出异常
    int savedState = fullyRelease(node);
    //超时时间节点
    final long deadline = System.nanoTime() + nanosTimeout;
    boolean timedout = false; //标记是否超时
    int interruptMode = 0;
    //置于条件队列上的节点如果正在在同步队列上等待重新获取锁,则返回true
    while (!isOnSyncQueue(node)) {
        if (nanosTimeout <= 0L) {
            //进入这里说明已经超过了等待时间,将节点转换进入同步队列并退出循环
            timedout = transferAfterCancelledWait(node);
            break;
        }
        if (nanosTimeout >= spinForTimeoutThreshold)
            //阻塞指定的时间
            LockSupport.parkNanos(this, nanosTimeout);
        //取消等待后检查中断,并返回对应的中断模式
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        //超时时间点减去当前系统时间即为需要等待的最大时间
        nanosTimeout = deadline - System.nanoTime();
    }
    //为队列中的线程获取独占不中断模式,如果在等待时中断并且中断模式不是THROW_IE,则将中断模式设置为REINTERRUPT
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        //断开条件队列中已经为取消状态的等待节点
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        //根据中断模式做不同的操作
        reportInterruptAfterWait(interruptMode);
    return !timedout; //返回是否超时
}

可以看到,方法中的大多数逻辑都与响应中断的await方法相同,唯一不同的就是这里使用用户指定的超时时间来完成超时等待这个功能。

最后我们来分析一下唤醒操作:
AbstractQueuedSynchronizer.ConditionObject:

public final void signal() {
    //需要在持有独占锁的前提下进行
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        /*从条件队列的第一个节点开始找到第一个不为取消状态的节点进行唤醒*/
        doSignal(first);
}

这里对当前线程是否持有独占锁的判断方法isHeldExclusively由子类实现,如果不支持condition,默认抛出UnsupportedOperationException。
AbstractQueuedSynchronizer.ConditionObject:

private void doSignal(Node first) {
    do {
        //将头结点置为给定头结点的下一个等待节点,并判断是否为null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null; //将条件队列的最后一个节点置为null标识当前条件队列已经没有等待节点
        first.nextWaiter = null; //将头结点的下一个节点置为null断开联系
    /*循环直到找到一个CONDITION状态的节点并将其成功从condition队列转移到同步队列或者遍历到了为null的节点*/
    } while (!transferForSignal(first) &&
       (first = firstWaiter) != null);
}

AbstractQueuedSynchronizer.ConditionObject:

final boolean transferForSignal(Node node) {
    //CAS改变节点等待状态,如果没有成功,说明节点已经被取消了
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    Node p = enq(node); //将节点拼入同步队列并返回前任节点
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        //如果前任节点已取消或尝试设置前任节点的waitStatus为SIGNAL失败,则唤醒节点的线程重新同步
        LockSupport.unpark(node.thread);
    return true;
}

ConditionObject还提供了可以唤醒全部等待节点的signalAll方法,与signal方法的区别在于后者只唤醒一个节点,前者会唤醒condition队列中的所有等待节点,流程非常相似,这里不过多赘述。

到这里,AQS ConditionObject源码分析就完成了。

猜你喜欢

转载自blog.csdn.net/heroqiang/article/details/79728210