Java并发包下的锁(4)——Condition接口

本文深入解析Java并发包中Condition接口的使用与实现原理,对比Object监视器,介绍等待/通知模式,展示Condition基本用法及其实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Condition 接口提供了类似Object的监视器方法,与Lock配合可以实现 等待/通知 模式

Condition的接口

在说Condition的接口之前,先对比一下与Object监视器的异同:

对比项Object的监视器(Monitor)Condition
前置条件获取对象的锁调用Lock.lock()获取锁
调用Lock.newCondition获取Condition对象
调用方式直接调用:object.wait()直接调用:condition.await()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持
当前线程释放锁并进入超时等待状态支持支持
当前线程释放锁并进入超时等待状态到将来某一个时间不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

 Condition接口的实现在两个队列同步器:AbstractQueuedSynchronizer和AbstractQueuedLongSynchronizer中,它们通过一个内部类ConditionObject聚合了其中的操作。下面来看看Condition提供的API有哪些:

// 调用该方法线程会释放锁并进入等待状态。调用该方法的线程要想进入运行状态的情况有:
// 其他线程调用该Condition的signal()或signalAll()方法,当前线程被选中唤醒
// 1. 其他线程中断当前线程
// 2. 如果当前等待线程从await()方法返回,表名该线程已经获取了Condition对象对应的锁
void await() throws InterruptedException;

// 对中断不敏感
void awaitUninterruptibly();

// 当前线程进入等待状态,直到被通知、中断或者超时才返回。返回值表示剩余的时间
long awaitNanos(long nanosTimeout) throws InterruptedException;

// 当前线程进入等待状态,直到被通知、中断或者到某一个时间。到了指定时间返回true,否则返回false
boolean awaitUntil(Date deadline) throws InterruptedException;

// 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁
void signal();

// 唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须与Condition相关联的锁
void signalAll();

下面的示例展示了Condition的基本用法:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

public void conditionWait() throws InterruptedException {
    lock.lock();
    try {
        condition.await()
    } finally {
        lock.unlock();
    }
}

public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        condition.signal()
    } finally {
        lock.unlock();
    }
}
Condition的实现

 ConditionObject 是同步器 AQS 的内部类,每个 Condition 对象都包含着一个队列,称为等待队列,该队列是 Condition 对象实现等待/通知功能的关键。

1. 等待队列

 等待队列是一个FIFO的队列,在队列中每个节点都包含了一个线程引用,该线程就是在 Condition 对象上等待的线程,如果一个线程调用了 Condition.await() 方法,那么该线程将会释放锁、构造成节点并加入等待队列进入等待状态。一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点(lastWaiter),当前线程调用 Condition.await() 方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。

image

 在节点的更新过程中,并不需要CAS的保证,因为调用await()方法的线程必定是已经获取了锁的线程,不会出现线程安全问题,该过程是由锁的机制来保证的。

 在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock(同步器)拥有一个同步队列和多个等待队列,如下图:

image

2. 等待

 当调用 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);
}

下图是当前线程释放同步状态加入等待队列的过程:

image

综合上面所讲,总结一下await()干了些什么事儿:

  • 当前线程为获取了锁的线程(同步队列中的首节点)

  • 将当前线程构造成新的节点(等待节点),并加入等待队列

  • 释放该线程的同步状态,唤醒同步对列中的后继节点,然后该线程在等待队列中等待

3. 通知

 调用Condition的signal()方法,将唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。signal() 的源码如下:

public final void signal() {
    // 如果当前线程没有获取到锁则抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 获取等待队列中的首节点
    Node first = firstWaiter;
    // 如果首节点不为空,将其移到同步队列,并调用LockSupport唤醒节点中的线程
    if (first != null)
        doSignal(first);
}

节点从等待队列移动到同步队列的过程如下:

image

 成功获取同步状态后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功获取了锁。Condition的signalAll()方法,相当于对等待队列中的每一个节点均执行一次signal()方法,其结果就是将等待队列中的所有节点全部移入到同步队列中,并唤醒每个节点的线程。

参考

《Java并发编程的艺术》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值