欢迎来到《[并发王者课](https://juejin.cn/post/6967277362455150628)》,本文是该系列文章中的**第19篇**。

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

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

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

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

```java
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是`wait`、`notify`和`notifyAll`增强版本,`wait`和`notify`有的能力它都有,`wait`和`notify`没有的能力它也有**。

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

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

* `await(long time, TimeUnit unit)`:让当前线程进入等待,直到收到信号或者被中断,或者到达指定的等待超时时间;
* `awaitNanos(long nanosTimeout)`:让当前线程进入等待,直到收到信号或者被中断,或者到达指定的等待超时时间,只是在时间单位上和上一个方法有所区别;
* `awaitUninterruptibly()`:**让当前线程进入等待,直到收到信号。注意,这个方法对中断是不敏感的**;
* `awaitUntil(Date deadline)`:**让当前线程进入等待,直到收到信号或者被中断,或者到达截止时间**;
* `signal()`:随机唤醒一个线程;
* `signalAll()`:唤醒所有等待的线程。

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

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

![](https://writting.oss-cn-beijing.aliyuncs.com/2021/06/27/16247728628301.jpg)

## 小结

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

|对比项|Object's Monitor methods| Condition
|---|---|---|
|**前置条件**|获取对象的锁|调用Lock获取锁,调用lock.newCondition()获取Condition对象|
|**调用方式**|直接调用,如object.wait()|直接调用,如condition.await()|
|**等待队列个数**|一个|**多个**|
|**当前线程释放锁并进入等待状态**|︎|︎|
|**当前线程释放锁并进入等待状态,在等待时不响应中断**|✘|**︎**|
|**当前线程释放锁并进入超时等待**|︎|︎|
|**当前线程释放锁并进入等待到未来某个时刻**|✘|**︎**|
|**唤醒等待队列中的某一个线程**|︎|︎|
|**唤醒等待队列中的全部线程**|︎|︎|

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

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

**夫子的试炼**

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

**延伸阅读与参考资料**

* 小结表格中的内容由网络图片提取,未能找到原始出处,知道的请评论告知,感谢!

* [《并发王者课》大纲与更新进度总览](https://juejin.cn/post/6967277362455150628)

**最新修订及更好阅读体验**

* [阅读掘金原文](https://juejin.cn/post/6976063081751248909/)

**关于作者**

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

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

并发王者课-铂金6:青出于蓝-Condition如何把等待与通知玩出新花样的更多相关文章

  1. 并发王者课-铂金8:峡谷幽会-看CyclicBarrier如何跨越重峦叠嶂

    欢迎来到<并发王者课>,本文是该系列文章中的第21篇,铂金中的第8篇. 在上一篇文章中,我们介绍了CountDownLatch的用法.在协调多线程的开始和结束时,CountDownLatc ...

  2. 并发王者课-铂金1:探本溯源-为何说Lock接口是Java中锁的基础

    欢迎来到<并发王者课>,本文是该系列文章中的第14篇. 在黄金系列中,我们介绍了并发中一些问题,比如死锁.活锁.线程饥饿等问题.在并发编程中,这些问题无疑都是需要解决的.所以,在铂金系列文 ...

  3. 并发王者课-铂金9:互通有无-Exchanger如何完成线程间的数据交换

    欢迎来到<并发王者课>,本文是该系列文章中的第22篇,铂金中的第9篇. 在前面的文章中,我们已经介绍了ReentrantLock,CountDownLatch,CyclicBarrier, ...

  4. 并发王者课-铂金10:能工巧匠-ThreadLocal如何为线程打造私有数据空间

    欢迎来到<并发王者课>,本文是该系列文章中的第23篇,铂金中的第10篇. 说起ThreadLocal,相信你对它的名字一定不陌生.在并发编程中,它有着较高的出场率,并且也是面试中的高频面试 ...

  5. 并发王者课-铂金2:豁然开朗-“晦涩难懂”的ReadWriteLock竟如此妙不可言

    欢迎来到<并发王者课>,本文是该系列文章中的第15篇. 在上篇文章中,我们介绍了Java中锁的基础Lock接口.在本文中,我们将介绍Java中锁的另外一个重要的基本型接口,即ReadWri ...

  6. 并发王者课 - 青铜4:synchronized用法初体验

    在前面的文章<双刃剑-理解多线程带来的安全问题>中,我们提到了多线程情况下存在的线程安全问题.本文将以这个问题为背景,介绍如何通过使用synchronized关键字解这一问题.当然,在青铜 ...

  7. 并发王者课 - 青铜 2:峡谷笔记 - 简单认识Java中的线程

    在前面的<兵分三路:如何创建多线程>文章中,我们已经通过Thread和Runnable直观地了解如何在Java中创建一个线程,相信你已经有了一定的体感.在本篇文章中,我们将基于前面的示例代 ...

  8. 并发王者课-青铜5:一探究竟-如何从synchronized理解Java对象头中的锁

    在前面的文章<青铜4:synchronized用法初体验>中,我们已经提到锁的概念,并指出synchronized是锁机制的一种实现.可是,这么说未免太过抽象,你可能无法直观地理解锁究竟是 ...

  9. 并发王者课-青铜7:顺藤摸瓜-如何从synchronized中的锁认识Monitor

    在前面的文章中,我们已经体验过synchronized的用法,并对锁的概念和原理做了简单的介绍.然而,你可能已经察觉到,有一个概念似乎总是和synchronized.锁这两个概念如影相随,很多人也比较 ...

随机推荐

  1. [网络编程之Socket套接字介绍,套接字工作流程,基于TCP协议的套接字程序]

    [网络编程之Socket套接字介绍,套接字工作流程,基于TCP协议的套接字程序] 为何学习socket套接字一定要先学习互联网协议: 1.首先:要想开发一款自己的C/S架构软件,就必须掌握socket ...

  2. Duplicate entry '' for key 'PRIMARY'

    今天在在mysql中插入数据 因为直接插入查询出来的表格,insert into 表(student_id,class_id) 直接插入了这两个字段对应的查询出来的表 没有留意到该表的主键没有设置自增 ...

  3. [java] IDEA

    快捷键 shift+shift:搜索 ctrl+d:复制行 ctrl+x:删除行 ctrl+p:查看方法参数 ctrl+o:查看类方法 ctrl+h:查看类结构 ctrl+i:实现接口方法 ctrl+ ...

  4. [bug] Mysql 对实体 "characterEncoding" 的引用必须以 ';' 分隔符结尾。

    参考 https://blog.csdn.net/cherrycheng_/article/details/51251441?

  5. 利用rsync备份生产应用(一)

    rsync简单介绍 Rsync(remote synchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件.Rsync使用所谓的"Rsync算法"来 ...

  6. 企业案例-Mysql误删除用户表如何恢复

    1.不小心删除了mysql所有用户 mysql> delete from mysql.user where 1=1; Query OK, 5 rows affected (0.00 sec) # ...

  7. LT4020替代方案

    国产  替代LT4020的方案 南芯 展讯的方案 https://item.taobao.com/item.htm?spm=a230r.1.14.21.6f27bf96rrAtci&id=56 ...

  8. Java中生成唯一标识符的方法(Day_48)

    有时候业务需要生成唯一标识符,但又不能依赖于数据库中自动递增的字段产生唯一ID,比如多表同一字段需要统一一个唯一ID,此时我们就需要用程序来生成一个唯一的全局ID. UUID UUID是指在一台机器上 ...

  9. JDK5.0新特性 (Day_07)

      JDK5.0新特性   目录 静态导入 自动装箱/拆箱 for-each循环 可变参数 枚举 JDK 5.0 新特性简介 JDK 5.0 的一个重要主题就是通过新增一些特性来简化开发,这些特性包括 ...

  10. 第11讲 | TCP协议(上):因性恶而复杂,先恶后善反轻松

    第11讲 | TCP协议(上):因性恶而复杂,先恶后善反轻松 TCP 包头格式 我们先来看 TCP 头的格式.从这个图上可以看出,它比 UDP 复杂得多. 首先,源端口号和目标端口号是不可少的,这一点 ...