Java5等待通知工具类Condition源码分析

Condition

Java5没有Lock之前,我们使用synchronized来控制同步,使用Object上的wait()、wait(long timeout)、notify()、notifyAll()方法二者进行配合使用,实现等待/通知模式。
Java5之后出现了Condition接口,也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。

对比项 Object Monitor Methods Condition
前置条件 获取对象的锁 调用Lock.lock()获得锁
调用Lock.newCondition()获取Condition对象
调用方式 Object.wait() Condition.await()
等待队列格式 一个 多个
当前线程释放锁并进入等待状态 支持 支持
当前线程释放锁并进入等待,在等待过程中不响应中断 不支持 支持
当前线程释放锁并进入超时等待 支持 支持
当前线程释放锁并等待至某个时间点 不支持 支持
唤醒等待队列中的一个线程 支持 支持
唤醒等待队列中的所有线程 支持 支持

基本方法

  • await():使当前线程在接收到信号或者被中断之前一直处于等待状态
  • await(long time,Time unit):使当前线程在接收到信号、被中断或到达等待时间之前一直处于等待状态
  • long awaitNanos(long nanosTimeout):使当前线程在接收到信号、被中断或到达等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimeout之前被唤醒,那么返回值就是nanosTimeout-消耗的时间;如果返回值小于0就说明已经超时了
  • awaitUninterruptibly():使当前线程在接收到信号之前一直处于等待状态,对中断不敏感
  • 当前线程进入等待状态直到被通知、中断或者到达指定的时间。如果没有到达指定时间就被唤醒,返回true,否则返回false
  • void signle():唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁
  • void signalAll():唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁

Condition实现分析

ConditionObject是AQS的内部类,因为Condition的操作需要获取相关联的锁,,因此作为同步器的内部类也很合理。
每个Condition对象都包含着一个等待队列,是Condition对象实现等待/通知的功能的关键。

等待队列

等待队列是一个FIFO的队列,队列中的每一个节点都包含了一个线程引用,该线程引用就是Condition对象上等待的线程,如果一个线程调用了await()或者await开头的方法,那么该线程将会释放锁、构造节点加入等待队列中并进入等待状态。等待队列的节点复用了AQS中的同步队列的节点内部类Node。

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /** First node of condition queue. */
    //等待队列中的头节点
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    //等待队列中的尾节点
    private transient Node lastWaiter;
    
    /**省略**/
复制代码

在这里插入图片描述
如图所示,Condition拥有首尾节点的引用,新增节点只需要将尾节点的next指向新增节点,并更新尾节点的引用即可。更新和插入并没有使用CAS,因为调用await方法的线程一定获取到了锁对象,是线程安全的。

等待

调用Condition的await开头的方法会使当前线程进入等待队列并释放锁,当从await()方法返回时,当前线程一定是获取了Condition相关的锁。

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;
        }
    }
    //自旋获得同步状态/锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    //清理等待队列中不是在等待条件的节点
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
复制代码

此段代码逻辑为:

  1. 判断当前线程是否已中断
  2. 未中断就构造一个节点添加到等待队列中
  3. 释放锁
  4. 判断当前节点释放在同步队列中,如果没有就一直循环判断,直到在同步队列中才可以去竞争同步状态
  5. 清理等待队列中不是在等待条件的节点

addConditionWaiter

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //判断等待队列的尾节点是否不是CONDITION状态,那么就清除
    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;//否则将尾节点的next指向当前节点
    lastWaiter = node;//把当前节点的引用赋给尾节点
    return node;
}
复制代码

通知

调用Condition的signal方法将会唤醒在等待队列中等待时间最长的节点(头节点),在唤醒之前会先把它移到同步队列中去。

signal

public final void signal() {
    if (!isHeldExclusively())//判断当前线程是否已经获得锁了
        throw new IllegalMonitorStateException();
    //得到头节点
    Node first = firstWaiter;
    if (first != null)
        //唤醒头节点
        doSignal(first);
}
复制代码

doSignal

private void doSignal(Node first) {
    do {
        //修改头节点
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
            (first = firstWaiter) != null);
}
复制代码

transferForSignal

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    //CAS操作将当前节点状态从CONDITION改为0
    //修改不成功说明已经被取消了
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        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).
     */
    //调用AQS的enq方法将当前节点添加到同步队列中去
    Node p = enq(node);
    int ws = p.waitStatus;
    //如果节点的状态为CANCEL或者修改状态为SIGNAL失败则直接唤醒
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
复制代码

通知的流程:

  1. 判断当前线程是否已经获得锁了
  2. 将头节点取出来
  3. 将等待队列中的头节点修改为原来的头节点的后继节点
  4. 修改当前节点的状态为从CONDITION改为0初始状态,修改不成功就返回false
  5. 将当前节点加入到同步队列中去
  6. 判断当前节点的状态是否为CANCEL或者修改状态为SIGNAL失败,则直接唤醒

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue()返回true),进而调用同步器的acquireQueued方法不断自旋直到获得锁。

signalAll方法相当于对每个方法都执行了一遍signal方法。