BlockingQueue 的 take() 内部实现:解析 AQS notEmpty 条件变量的挂起与唤醒逻辑

阿晨小哥_7749

阿晨小哥_7749

2026-05-31

943人浏览

原创

take()方法在队列为空时通过AQS的notEmpty条件变量阻塞:先释放锁,再入条件队列;被signal后转入同步队列重新争锁,全程原子、可中断且支持多条件精确唤醒。

blockingqueue 的 take() 内部实现:解析 aqs notempty 条件变量的挂起与唤醒逻辑

BlockingQueue 的 take() 方法在队列为空时会阻塞,其核心依赖于 AQS(AbstractQueuedSynchronizer)中的条件变量 notEmpty。这个过程不是简单地调用 wait(),而是通过 AQS 的独占锁 + 条件队列协作完成挂起与唤醒——关键在于「先释放锁、再入条件队列、被 signal 后重新竞争锁」这一闭环。

notEmpty 条件变量的创建与归属

notEmpty 是一个 ConditionObject 实例,由 ReentrantLock 创建并绑定到其内部的 AQS 上。它不独立存在,必须依附于某个独占锁(即 BlockingQueue 实现中使用的 lock,如 ArrayBlockingQueue 中的 lock)。所有对 notEmpty.await() 和 notEmpty.signal() 的调用,都需在持有该锁的前提下进行。

  • 每个 ConditionObject 维护自己的条件队列(FIFO),节点类型是 Node.CONDITION
  • 条件队列和 AQS 同步队列(sync queue)物理隔离,但逻辑联动:await 时节点从 sync queue 出队,进入 condition queue;signal 时节点从 condition queue 转移到 sync queue 尾部
  • notEmpty 语义上表示“队列非空”,所以只有在成功出队后(即 take 真正拿到元素),才会触发 notEmpty.signal() —— 这通常发生在 put() 或其它修改队列的操作里

take() 中 await 的完整挂起流程

以 ArrayBlockingQueue 为例,take() 执行路径为:lock.lockInterruptibly() → 检查队列是否为空 → 若空则调用 notEmpty.await() → 挂起当前线程。

  • 调用 await() 前,线程已持有 lock(即已进入 AQS 同步队列并获得锁)
  • await() 内部首先将当前线程构造成 Node.CONDITION 节点,插入到 notEmpty 的条件队列尾部
  • 然后完全释放锁(调用 fullyRelease(node)),这会导致 AQS 的 state 归零,并唤醒同步队列中下一个等待者(如有)
  • 最后线程进入 WAITING 状态,不再参与 CPU 竞争,直到被 signal() 唤醒或中断

signal() 如何触发线程重新竞争锁

当另一个线程执行 put() 并成功入队后,会调用 notEmpty.signal() —— 注意这不是唤醒任意线程,而是唤醒条件队列头节点。

  • signal() 先将条件队列头节点(Node.CONDITION)从条件队列中移除
  • 接着调用 enq(node) 将其作为普通节点加入 AQS 同步队列尾部,并设置其 waitStatus 为 0
  • 此时该节点尚未获得锁,只是获得了“排队资格”;它需要像其他同步队列节点一样,等待前驱节点释放锁后,才能尝试 acquire
  • 一旦轮到它 acquire,会再次尝试获取锁(即 CAS 修改 state),成功则继续执行 take() 剩余逻辑(取元素、返回)

为什么不能直接用 Object.wait()/notify()?

AQS 的条件变量解决了内置锁条件机制的几个硬伤:

  • 支持多个独立条件队列(如 notEmpty 和 notFull),而 synchronized 只有一个 wait set,易导致信号错乱
  • 精确控制唤醒目标(signal 而非 notifyAll),避免惊群效应
  • 挂起前自动释放锁,唤醒后自动重入锁,整个过程原子且可中断(await 可响应 interrupt)
  • 与 AQS 同步语义深度集成,能统一处理超时、中断、公平/非公平策略等

理解 notEmpty 的 await/signal 协作,本质是理解 AQS 如何把「等待某个业务条件成立」这一高层语义,拆解为「释放锁 → 进条件队列 → 被转移至同步队列 → 重新争锁」这一底层状态机。它不神秘,但每一步都紧扣锁与条件的分离与协同。

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

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

下载

相关标签:

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

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

3509

3

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

2023.07.25

2408

9

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

2023.08.02

1340

5

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.09

1298

4

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

2023.09.05

1396

5

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2298

7

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

2023.09.20

3780

8

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

2023.09.22

17675

6

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

549

3

热门下载

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

精品课程

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

共6课时 | 54.7万人学习

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

共89课时 | 134.1万人学习