写点什么

并发王者课 - 铂金 6:青出于蓝 -Condition 如何把等待与通知玩出新花样

发布于: 2021 年 07 月 05 日
并发王者课-铂金6:青出于蓝-Condition如何把等待与通知玩出新花样

欢迎来到《并发王者课》,本文是该系列文章中的第 19 篇


在上一篇文章中,我们介绍了阻塞队列。如果你阅读过它的源码,那么你一定会注意到源码有两个 Condition 类型的变量:notEmptynotFull,在读写队列时你也会注意到它们是如何被使用的。事实上,在使用 JUC 中的各种锁时,Condition 都很有用场,你很有必要了解它。所以,本文就为你介绍它的来龙去脉和用法。


在前面的系列文章中,我们多次提到过synchronized关键字,相信你已经对它的用法了如于心。在多线程协作时,有两个另外的关键字经常和synchronized一同出现,它们相互配合,就是waitnotify,比如下面的这段代码:


public class CountingSemaphore {  private int signals = 0;  public synchronized void take() {    this.signals++;    this.notify();  // 发送通知  }  public synchronized void release() throws InterruptedException {    while (this.signals == 0)      this.wait(); // 释放锁,进入等待    This.signals--;  }}
复制代码


synchronized是 Java 的原生同步工具,waitnotify是它的原生搭档。然而,在铂金系列中,我们已经开始了 Lock 接口和它的一些实现,比如可重入锁 ReentrantLock 等。相比于synchronized,JUC 所封装的这些锁工具在功能上要丰富得多,也更加容易使用。所以,相应的配套自然也要跟上,于是 Condition 就应运而生。


比如在上文的阻塞队列中,Condition 就已经闪亮登场:


public class LinkedBlockingQueue < E > extends AbstractQueue < E >    implements BlockingQueue < E > , java.io.Serializable {
...省略源码若干
// 定义Condition // 注意,这里定义两个Condition对象,用于唤醒不同的线程 private final Condition notEmpty = takeLock.newCondition(); private final Condition notFull = putLock.newCondition();
public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { // 进入等待 notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { // 发送唤醒信号 notEmpty.signal(); } finally { takeLock.unlock(); } } ...省略源码若干
}
复制代码


从功能定位上说,作为 Lock 的配套工具,Condition 是waitnotifynotifyAll增强版本,waitnotify有的能力它都有,waitnotify没有的能力它也有


JUC 中的 Condition 是以接口的形式出现,并定义了一些核心方法:


  • await():让当前线程进入等待,直到收到信号或者被中断;

  • await(long time, TimeUnit unit):让当前线程进入等待,直到收到信号或者被中断,或者到达指定的等待超时时间;

  • awaitNanos(long nanosTimeout):让当前线程进入等待,直到收到信号或者被中断,或者到达指定的等待超时时间,只是在时间单位上和上一个方法有所区别;

  • awaitUninterruptibly()让当前线程进入等待,直到收到信号。注意,这个方法对中断是不敏感的

  • awaitUntil(Date deadline)让当前线程进入等待,直到收到信号或者被中断,或者到达截止时间

  • signal():随机唤醒一个线程;

  • signalAll():唤醒所有等待的线程。


从 Condition 的核心方法中可以看到,相较于原生的通知与等待,它的能力明显增强了很多,比如awaitUninterruptibly()awaitUntil()。另外,Condition 竟然是可以唤醒指定线程的,这就很有意思


作为接口,我们并不需要手动实现 Condition,JUC 已经提供了相关的实现,你可以在 ReentrantLock 中直接使用它。相关的类、接口之间的关系如下所示:


小结

以上就是关于 Condition 的全部内容。Condition 并不复杂,它是 JUC 中 Lock 的配套,在理解时要结合原生的waitnotify去理解。关于 Condition 与它们之间的详细区别,已经都在下面的表格里:



理解表格中的各项差异,不要死记硬背,而是要基于 Condition 接口中定义的方法,从关键处理解它们的不同。


正文到此结束,恭喜你又上了一颗星✨


夫子的试炼


  • 编写代码使用 Condition 唤醒指定线程。


延伸阅读与参考资料



最新修订及更好阅读体验



关于作者


关注【技术八点半】,及时获取文章更新。传递有品质的技术文章,记录平凡人的成长故事,偶尔也聊聊生活和理想。早晨 8:30 推送作者品质原创,晚上 20:30 推送行业深度好文。


如果本文对你有帮助,欢迎点赞关注监督,我们一起从青铜到王者

发布于: 2021 年 07 月 05 日阅读数: 3
用户头像

微信公众号:【技术八点半】 2018.05.13 加入

关注公众号【技术八点半】,及时获取文章更新。传递有品质的技术文章,记录平凡人的成长故事,偶尔也聊聊生活和理想。早晨8:30推送作者品质原创,晚上20:30推送行业深度好文。

评论

发布
暂无评论
并发王者课-铂金6:青出于蓝-Condition如何把等待与通知玩出新花样