需要两个condition以实现精准唤醒:notempty用于消费者等待非空,notfull用于生产者等待非满,避免单condition下的虚假唤醒和无谓唤醒,确保每类线程只响应对应状态变化。

ArrayBlockingQueue 使用两个 Condition 实例(notEmpty 和 notFull)实现高效的线程协作,本质是通过分离“等待非空”和“等待非满”两类阻塞逻辑,避免单 Condition 下的虚假唤醒与无谓唤醒。
为什么需要两个 Condition?
如果只用一个 Condition,比如 wait() 时无法区分是因队列为空还是已满而等待。消费者调用 take() 阻塞后,生产者 put() 成功并 signal(),但该 signal() 可能唤醒另一个也正在 take() 的消费者——这没问题;可如果此时唤醒的是一个正想 put() 的生产者,它发现队列已满,只能继续 await,造成浪费。双 Condition 让每类线程只响应对应状态变化,唤醒精准。
notEmpty 与 notFull 的典型协作流程
以 put() → take() 为例:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- put() 成功入队后,若之前有线程在 notEmpty 上 await,则调用 notEmpty.signal() 唤醒一个等待消费的线程
- take() 成功出队后,若之前有线程在 notFull 上 await,则调用 notFull.signal() 唤醒一个等待生产的线程
- 两者互不干扰:notEmpty.signal() 不影响 notFull 的等待队列,反之亦然
底层依赖 ReentrantLock 与 Condition 的契约
ArrayBlockingQueue 内部使用独占锁 ReentrantLock,两个 Condition 均由同一 lock 创建:
- 每个 Condition 维护独立的等待队列(AQS 的 ConditionQueue)
- await() 会释放 lock 并将当前线程加入对应 Condition 的队列;signal() 将线程从该队列移至 lock 的同步队列,等待重新获取锁
- 正因为 lock 相同,两个 Condition 才能共享同一把锁的状态,确保操作原子性(如 size 更新、入队/出队)
注意 signal() 与 signalAll() 的选择
ArrayBlockingQueue 对两个 Condition 都使用 signal()(而非 signalAll):
- 因为每次仅有一个线程能成功操作(如 take() 只能取一个元素),唤醒一个足够;多余唤醒反而增加竞争
- 但这也意味着:若多个线程在 notEmpty 上 await,只有其中一个会被唤醒,其余仍需等待下次 signal —— 这正是“精确唤醒”的设计意图
- 极端情况下(如突发大量消费者 await),可能产生短暂饥饿,但实际场景中影响极小
这种双 Condition 模式不是 ArrayBlockingQueue 特有,而是生产者-消费者问题的标准解法之一,核心在于状态解耦与唤醒定向。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










