Condition源码分析

Condition

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

对比项Object Monitor MethodsCondition
前置条件获取对象的锁调用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方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹氿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值