怎么分析 BlockingQueue 的 put 与 take 在条件变量 Condition 上的阻塞唤醒机制

小明吖_3163

小明吖_3163

2026-04-23

862人浏览

原创

put 和 take 必须用 Condition 而非 wait/notify,因为二者需独立等待「队列未满」和「队列非空」两个条件;wait/notify 只能绑定单个监视器,易导致错误唤醒或死锁,而 Condition 支持同一锁下多条件队列,实现精准唤醒。

怎么分析 blockingqueue 的 put 与 take 在条件变量 condition 上的阻塞唤醒机制

put 和 take 为什么必须用 Condition 而不是 wait/notify

因为 wait() 和 notify() 只能绑定在单个对象监视器上,而 put 和 take 需要两个独立的等待条件:「队列未满」和「队列非空」。如果共用一个锁对象并混用 notifyAll(),就会唤醒错误类型的线程(比如 put 成功后唤醒另一个 put 线程),造成无效调度甚至死锁。

Condition 允许为同一把锁创建多个等待队列:notFull 和 notEmpty 就是典型例子。每个 await() 调用只挂入对应条件队列,signal() 也只唤醒该队列里的线程 —— 这是精准唤醒的前提。

  • ArrayBlockingQueue 用一把 ReentrantLock + 两个 Condition 实现
  • LinkedBlockingQueue 用两把锁(putLock / takeLock)+ 各自配套的 Condition,进一步解耦生产/消费路径
  • 手写时若误用 Object.wait(),又没配对 while 循环检查条件,极易触发虚假唤醒后直接操作空/满队列

put 方法阻塞时到底在等什么、谁来唤醒它

put() 在队列满(count == capacity)时调用 notFull.await(),当前线程释放锁、进入 notFull 条件队列挂起。它不会被 take() 的任意执行唤醒,而是严格依赖 take() 中的 notFull.signal() —— 且仅当 take() 真正腾出一个空位(即 count 减少后 )才会触发。

  • 唤醒时机:消费者成功取出一个元素,且取出后 count 仍小于容量 → 才调 notFull.signal()
  • 不唤醒的情况:消费者取完后队列刚好为空(count == 0),此时只唤醒 notEmpty 队列,notFull 保持静默
  • 注意 signal() 不保证立即执行:被唤醒线程需重新竞争锁,拿到锁后仍要再次检查 isFull(),防止因竞争或中断导致状态已变

take 方法被唤醒后为何还要 while 循环判断 isEmpty

take() 中的 while (isEmpty()) { notEmpty.await(); } 不是可选写法,是强制要求。原因有三:

  • 虚假唤醒:JVM 或 OS 可能在无 signal 时唤醒线程,此时队列可能依然为空
  • 多线程竞争:多个 take 线程同时被 signal() 唤醒,但只有一个能成功取走元素,其余必须继续等
  • 中断干扰:线程被中断后 await() 抛异常退出,但逻辑不能跳过条件检查直接执行取操作

所以不能写成 if (isEmpty()) await() —— 一旦漏掉循环,就等于默认“唤醒即条件成立”,实际中会立刻抛 NullPointerException 或返回 null。

LinkedBlockingQueue 的双锁如何影响 Condition 使用

LinkedBlockingQueue 把 putLock 和 takeLock 分开,意味着 notFull 绑定在 putLock 上,notEmpty 绑定在 takeLock 上。这带来两个关键影响:

  • put() 中唤醒 notEmpty 必须先获取 takeLock,否则无法调用 notEmpty.signal() —— 源码里能看到 signalNotEmpty() 方法内部加了 takeLock.lock()
  • 双锁提升吞吐量:生产者和消费者几乎不争同一把锁,但代价是 count 变成跨锁共享变量,必须用 AtomicInteger 保证读写原子性
  • 调试时容易忽略:比如在 put() 中打日志发现 notEmpty.signal() 没生效,其实是卡在了 takeLock 获取上,而非信号丢失

真正容易被忽略的是:Condition 的生命周期完全依附于锁。一旦锁被销毁或重置(比如某些自定义实现中反复 new ReentrantLock),其关联的 Condition 就失效了 —— 这类问题在线上环境极难复现,但会导致线程永久挂起。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

12037

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

9122

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

7812

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1264

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

1028

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1516

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

3149

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

20391

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1375

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习