Condition的概念

回忆 synchronized 关键字,它配合 Object 的 wait()、notify() 系列方法可以实现等待/通知模式。

对于 Lock,通过 Condition 也可以实现等待/通知模式。

Condition 是一个接口。 

Condition 接口的实现类是 Lock(AQS)中的 ConditionObject。 

Lock 接口中有个 newCondition() 方法,通过这个方法可以获得 Condition 对象(其实就是 ConditionObject)。 

因此,通过 Lock 对象可以获得 Condition 对象。

1Lock lock  = new ReentrantLock();
2Condition c1 = lock.newCondition();
3Condition c2 = lock.newCondition();


Condition的实现分析

实现

ConditionObject 类是 AQS 的内部类,实现了 Condition 接口。

1public class ConditionObject implements Conditionjava.io.Serializable {
2        private transient Node firstWaiter;
3        private transient Node lastWaiter;
4        ...

可以看到,等待队列和同步队列一样,使用的都是同步器 AQS 中的节点类 Node。 
同样拥有首节点和尾节点, 
每个 Condition 对象都包含着一个 FIFO 队列。 
结构图:

条件队列Condition的实现分析?_条件队列

等待

调用 Condition 的 await() 方法会使线程进入等待队列,并释放锁,线程状态变为等待状态。

 1public final void await() throws InterruptedException {
2    if (Thread.interrupted())
3        throw new InterruptedException();
4    Node node = addConditionWaiter();
5    //释放同步状态(锁)
6    int savedState = fullyRelease(node);
7    int interruptMode = 0;
8    //判断节点是否放入同步对列
9    while (!isOnSyncQueue(node)) {
10        //阻塞
11        LockSupport.park(this);
12        //如果已经中断了,则退出
13        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
14            break;
15    }
16    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
17        interruptMode = REINTERRUPT;
18            if (node.nextWaiter != null// clean up if cancelled
19            unlinkCancelledWaiters();
20            if (interruptMode != 0)
21                reportInterruptAfterWait(interruptMode);
22}

分析上述方法的大概过程:

  • 将当前线程创建为节点,加入等待队列;

  • 释放锁,唤醒同步队列中的后继节点;

  • while循环判断节点是否放入同步队列:

  1. 没有放入,则阻塞,继续 while 循环(如果已经中断了,则退出)

  2. 放入,则退出 while 循环,执行后面的判断

  • 退出 while 说明节点已经在同步队列中,调用 acquireQueued() 方法加入同步状态竞争。

  • 竞争到锁后从 await() 方法返回,即退出该方法。

条件队列Condition的实现分析?_条件队列_02

addConditionWaiter() 方法:

 1private Node addConditionWaiter() {
2    Node t = lastWaiter;
3    if (t != null && t.waitStatus != Node.CONDITION) {
4        //清除条件队列中所有状态不为Condition的节点
5        unlinkCancelledWaiters();
6        t = lastWaiter;
7    }
8    //将该线程创建节点,放入等待队列
9    Node node = new Node(Thread.currentThread(), Node.CONDITION);
10    if (t == null)
11        firstWaiter = node;
12    else
13        t.nextWaiter = node;
14    lastWaiter = node;
15    return node;
16}

过程分析:同步队列的首节点移动到等待队列。加入尾节点之前会清除所有状态不为 Condition 的节点。


通知

调用 Condition 的 signal() 方法,可以唤醒等待队列的首节点(等待时间最长),唤醒之前会将该节点移动到同步队列中。

1public final void signal() {
2    //判断是否获取了锁
3    if (!isHeldExclusively())
4        throw new IllegalMonitorStateException();
5    Node first = firstWaiter;
6    if (first != null)
7        doSignal(first);
8}

过程:

  • 先判断当前线程是否获取了锁;

  • 然后对首节点调用 doSignal() 方法。

1private void doSignal(Node first{
2    do {
3        if ( (firstWaiter = first.nextWaiter) == null)
4            lastWaiter = null;
5        first.nextWaiter = null;
6    } while (!transferForSignal(first) &&
7       (first = firstWaiter) != null);
8}

过程:

  • 修改首节点;

  • 调用 transferForSignal() 方法将节点移动到同步队列。

 1final boolean transferForSignal(Node node) {
2    //将节点状态变为0   
3    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
4        return false;
5    //将该节点加入同步队列
6    Node p = enq(node);
7    int ws = p.waitStatus;
8    //如果结点p的状态为cancel 或者修改waitStatus失败,则直接唤醒
9    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
10        LockSupport.unpark(node.thread);
11    return true;
12}

调用同步器的 enq 方法,将节点移动到同步队列, 

满足条件后使用 LockSupport 唤醒该线程。

条件队列Condition的实现分析?_条件队列_03

当 Condition 调用 signalAll() 方法:

 1public final void signalAll() {
2    if (!isHeldExclusively())
3        throw new IllegalMonitorStateException();
4    Node first = firstWaiter;
5    if (first != null)
6        doSignalAll(first);
7}
8private void doSignalAll(Node first) {
9    lastWaiter = firstWaiter = null;
10    do {
11        Node next = first.nextWaiter;
12        first.nextWaiter = null;
13        transferForSignal(first);
14        first = next;
15    } while (first != null);
16}

可以看到 doSignalAll() 方法使用了 do-while 循环来唤醒每一个等待队列中的节点,直到 first 为 null 时,停止循环。

一句话总结 signalAll() 的作用:将等待队列中的全部节点移动到同步队列中,并唤醒每个节点的线程。


总结

整个过程可以分为三步:

第一步:一个线程获取锁后,通过调用 Condition 的 await() 方法,会将当前线程先加入到等待队列中,并释放锁。然后就在 await() 中的一个 while 循环中判断节点是否已经在同步队列,是则尝试获取锁,否则一直阻塞。

第二步:当线程调用 signal() 方法后,程序首先检查当前线程是否获取了锁,然后通过 doSignal(Node first) 方法将节点移动到同步队列,并唤醒节点中的线程。

第三步:被唤醒的线程,将从 await() 中的 while 循环中退出来,然后调用 acquireQueued() 方法竞争同步状态。竞争成功则退出 await() 方法,继续执行。


转载:https://mp.weixin.qq.com/s/76bFlXKahKaRrR5ceZPYNA