AQS-了解下等待队列和同步队列的交互

首先看一下ConditionObject类的相关属性:
AQS-了解下等待队列和同步队列的交互_第1张图片

可以发现ConditionObject类是Condition的子类,那么接着看看Condition:
AQS-了解下等待队列和同步队列的交互_第2张图片
发现就是个接口,里面提供了一些方法,根据方法名可以猜测,大致是阻塞等待和唤醒的方法,暂不深入,留个印象。

返回ConditionObject类看看它有什么切入口可以让我们去理解它的作用,看看它拥有的方法:
AQS-了解下等待队列和同步队列的交互_第3张图片
发现还是看不出什么来,那么换一个切入点,找一个AQS子类相关的同步器来作为突破口,这里使用ReentrantLock,那么就先找找ReentrantLock哪里用到了ConditionObject类:
在这里插入图片描述
AQS-了解下等待队列和同步队列的交互_第4张图片
AQS-了解下等待队列和同步队列的交互_第5张图片
从上述截图可以发现,ReentrantLock类的newCondition()方法会返回一个ConditionObject对象。

那么ConditionObject对象的创建来源就找到了,那么找找ReentrantLock是怎么使用它的?回想之前截图中Condition接口提供的方法,ConditionObject对象的使用肯定是围绕着这些方法做展开和扩展的,先看看Condition接口的await()方法在子类的具体实现:

public final void await() throws InterruptedException {
     
    if (Thread.interrupted())
        throw new InterruptedException();
   //这里会新建出一个node对象
    Node node = addConditionWaiter();
   //释放同步状态,也就是释放锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
     
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

而队列是怎么被创建的,主要跟进addConditionWaiter()这个方法:

//该方法的主要作用就是找出有效的尾节点,如果有效尾节点为null,则初始化队列,队列头尾节点都是要新加入的线程相关节点,否则新节点设置为尾节点
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;
}

Condition对象必须通过Lock对象的newCondition()方法才能获取,通过上述的addConditionWaiter()方法,只要获取到同步状态的首节点调用了await()方法,该节点相关的线程就会被重新封装成一个新的节点,最后加入到ConditionObject相关的队列中,再看看AQS同步器的队列,也有维护头节点和尾节点对象,如下:
AQS-了解下等待队列和同步队列的交互_第6张图片
他们也是由一个个Node对象连接起来的链表,只不过AQS本身的队列是双向链表,而ConditionObject相关的队列是单向的。

现在可以知道AQS中的首节点获取到同步状态后,调用await(),就会从AQS的队列进入到ConditionObject的队列,那么ConditionObject的队列中的节点又是怎么进入到AQS的队列的?

接下来看看signal()方法:

public final void signal() {
     
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
   //取得ConditionObject队列中的首节点
    Node first = firstWaiter;
  //首节点不为null,说明完成了初始化,同时队列中有节点存在
    if (first != null)
        doSignal(first);
}

//该方法主要作用是找到没有被取下的节点,如果唤醒失败,说明节点被取消了,那么继续往下找
private void doSignal(Node first) {
     
    do {
     
       //这里先取得首节点的后继节点赋值给firstWaiter,firstWaiter为null,则说明队列中只有一个节点了
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        //把首节点的后继节点设为null,因为首节点要出队了
        first.nextWaiter = null;
     //循环做唤醒操作,唤醒失败,则说明节点被取消了,那么就把firstWaiter赋值成首节点,继续循环
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

重点在transferForSignal()方法:

final boolean transferForSignal(Node node) {
     
    //如果这步设置不成功,说明节点不处于Node.CONDITION状态,那就是被取消了,返回fasle,取消的节点无需唤醒
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    //如果设置成功,那么就调用AQS的enq()方法,这里简述下enq()方法作用,就是把节点设置为尾节点加入到AQS的队列中,这里很重要的一点需要提醒,返回的p对象是原来队列的尾节点,也就是现在新加入节点的前驱节点
    Node p = enq(node);
   //得到新加入节点的前驱节点的状态
    int ws = p.waitStatus;
   //如果该新节点的前驱节点已经被取消,或者设置等待状态失败,那么就唤醒新节点,重新去做同步,保证能拼接到有效的尾节点
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
       //唤醒新节点
        LockSupport.unpark(node.thread);
    return true;
}

所以当前线程调用了signal()方法,会取出ConditionObject队列中的有效首节点进入AQS队列中,成为AQS队列新的尾节点。

从以上方法可以得知ConditionObject的队列是如何和AQS的队列做交互的。

由于ConditionObject的队列存放的是等待的节点,所以可以把它的队列称之为等待队列,相反的AQS队列中的节点在自旋阻塞尝试获取同步状态,所以把AQS的队列称之为同步队列。

总结

ConditionObject的队列称为等待队列,AQS的队列称为同步队列,同步队列的首节点调用await()方法,该节点相关的线程会被封装成新节点进入等待队列,当前线程调用signal()方法,会取出等待队列中的首节点插入到同步队列的队尾,同步队列和等待队列就是如此做交互的。

你可能感兴趣的:(#,AQS面试,了解下等待队列和同步队列的交互)