07、Java并发编程:用"等待-通知"机制优化循环等待

本文基于JDK 8/17源码与《Java并发编程实战》经典理论,结合企业级生产环境真实案例,从痛点出发,系统讲透
wait/notify与Condition等待通知机制的原理、演进、实战与面试考点。读完即可上手,面试直接套用。
文章目录
- 07、Java并发编程:用"等待-通知"机制优化循环等待
-
- 一、痛点场景:你写的轮询,正在悄悄吃掉你的CPU
- 二、什么是等待-通知机制:专业解释、大白话、生活案例一起上
-
- [2.1 专业解释](#2.1 专业解释)
- [2.2 大白话翻译](#2.2 大白话翻译)
- [2.3 生活案例:外卖取餐](#2.3 生活案例:外卖取餐)
- 三、为什么需要它:从忙等到等待通知的演进史
-
- [3.1 第一代:死循环忙等](#3.1 第一代:死循环忙等)
- [3.2 第二代:sleep轮询](#3.2 第二代:sleep轮询)
- [3.3 第三代:Object.wait()/notify()(JDK 1.0起)](#3.3 第三代:Object.wait()/notify()(JDK 1.0起))
- [3.4 第四代:Condition(JDK 1.5 J.U.C包)](#3.4 第四代:Condition(JDK 1.5 J.U.C包))
- [3.5 第五代:BlockingQueue等高级工具](#3.5 第五代:BlockingQueue等高级工具)
- 四、怎么用:从基础语法到企业级实战代码
-
- [4.1 wait/notify 基础四板斧](#4.1 wait/notify 基础四板斧)
- [4.2 经典生产者消费者:wait/notify版本](#4.2 经典生产者消费者:wait/notify版本)
- [4.3 Condition版本:精准唤醒,企业级首选](#4.3 Condition版本:精准唤醒,企业级首选)
- [4.4 企业项目实战:订单异步处理管道](#4.4 企业项目实战:订单异步处理管道)
- 五、主流方案横向对比:wait/notify、Condition、LockSupport、BlockingQueue
- 六、常用场景盘点
- 七、面试官高频面试题(带答案要点)
- 八、总结
- 转载声明
一、痛点场景:你写的轮询,正在悄悄吃掉你的CPU
先看一个企业项目里几乎人人都写错过的场景。
假设你负责一个电商库存系统,有一个后台线程需要不停检查"仓库里有没有新货到了",一旦到货就立刻通知主业务线程去处理订单。新手第一反应是这么写:
java
// 错误写法一:死循环忙等,CPU直接跑满
while (true) {
if (stock > 0) {
processOrder();
break;
}
}
这段代码一跑,你会发现服务器CPU使用率直接飙到100%。为什么?因为这个线程在疯狂空转,while(true) 以纳秒级速度反复判断 stock > 0,哪怕库存一直是0,CPU也一刻不停。这就是经典的忙等(Busy Waiting)。
那加个 sleep 呢?
java
// 错误写法二:sleep轮询,延迟高且浪费CPU
while (true) {
if (stock > 0) {
processOrder();
break;
}
Thread.sleep(1000); // 睡1秒再醒
}
好一点了,CPU降下来了,但新问题来了:
- 延迟不可控:如果库存刚在你睡下的第1ms就到了,你得等999ms才能发现,实时性完全靠运气。
- 粒度难权衡:睡短了CPU浪费,睡长了业务延迟。1秒?100ms?10ms?怎么选都是错。
- 多线程协作时更糟:如果有10个消费者线程都在轮询同一个库存队列,10个线程每秒醒来几百次,白白竞争CPU时间片。
- 无法精确唤醒:业务需要的是"货到了立刻叫我",而不是"我每隔一会儿自己去看看"。
这就是循环等待的性能黑洞。在并发编程的世界里,这种"靠反复检查来等待条件成立"的模式,本质上是在用CPU时间换简单性,而这个交易在生产环境里永远亏。
那正确姿势是什么?答案就是等待-通知机制(Wait/Notify):条件不满足时线程主动挂起释放CPU,条件满足时被精准唤醒。

二、什么是等待-通知机制:专业解释、大白话、生活案例一起上
2.1 专业解释
等待-通知机制是Java内置在线程间协作(Inter-Thread Communication)层面的一套协议。它的核心由两个角色组成:
- 等待方(Waiter) :当某个前置条件不满足时,线程调用
wait()(或Condition.await())主动进入等待队列,并释放持有的对象监视器锁,进入WAITING状态,不占用CPU。 - 通知方(Notifier) :当条件发生变化(比如生产了一个商品、队列非空了),线程调用
notify()(或Condition.signal())唤醒等待队列中的一个或所有线程,被唤醒的线程重新竞争锁,从wait()返回点继续执行。
这套机制的底层依托的是Java对象头中的Monitor(管程)模型。每个Java对象都有一个隐式的监视器锁,监视器内部维护两个集合:
- Entry Set(入口集合):等待争夺锁的线程队列。
- Wait Set(等待集合) :调用
wait()后挂起的线程队列。

2.2 大白话翻译
你可以把它想象成你去银行办业务:
- 你(等待方)到了柜台前,柜员说"你要办的那个对公业务现在办不了,主管不在"。你不会站在柜台前每隔1秒问一次"主管来了吗"(那是轮询),你会拿个号去休息区坐着等 (这就是
wait(),释放柜台让别人能用)。 - 过了半小时,主管回来了(条件满足了),柜员叫号喊你的号码 (这就是
notify())。 - 你听到叫号,重新排队上台(被唤醒后重新竞争锁),继续办你的业务。
对比一下:轮询就是你站在柜台前每隔1秒问一次"主管来了吗",既挡着后面的人(占着锁不放),又把自己累得够呛(CPU空转)。
2.3 生活案例:外卖取餐
再举个更贴近的例子。你点了外卖,取餐码是A001。
- 轮询模式:你每30秒冲到出餐口问一句"A001好了没",来回跑,腿都断了。
- 等待通知模式:你坐在座位上玩手机,骑手把餐放到出餐口后,前台大屏喊"A001取餐",你听到了才过去拿。
wait/notify 就是那个取餐叫号系统。它解决的核心矛盾只有一个:让等待的线程在无事可做时彻底睡着,不烧CPU;让条件就绪的瞬间有人立刻通知,不延迟。
三、为什么需要它:从忙等到等待通知的演进史
等待-通知机制不是凭空设计出来的,它是并发编程领域几十年踩坑踩出来的最优解。理解演进过程,你才能真正理解为什么要这么写。
3.1 第一代:死循环忙等
java
while (!condition) { } // 纯CPU空转
这是最原始的写法。线程占着CPU疯狂检查条件,直到条件成立。优点是绝对实时,缺点是CPU利用率灾难。在单核时代还能勉强接受(反正这个线程占满CPU),多核时代这就是犯罪。
3.2 第二代:sleep轮询
java
while (!condition) {
Thread.sleep(interval);
}
改进点是加入了休眠间隔,CPU消耗降下来了。但引入了三个新问题:
- 延迟由interval决定:你设100ms,最坏情况就要等100ms。
- 条件可能错过又重复:休眠期间条件变了,你不知道;醒来发现条件已经变了又恢复了,你白等一轮。
- 占用锁不释放:如果这个循环还持着锁,其他线程根本进不来改条件,死锁直接发生。
3.3 第三代:Object.wait()/notify()(JDK 1.0起)
这就是Java内置的等待通知机制。它的革命性在于两点:
- 等待时自动释放锁:这解决了"持锁等待导致条件永远改不了"的死锁问题。
- 被唤醒时自动重新竞争锁 :从
wait()返回时,线程已经重新拿到了锁,可以安全地检查条件。
但它有天生的局限性:
- 一个Object只有一个Wait Set,所有等待不同条件的线程挤在同一个队列里。
notify()随机唤醒一个,可能唤醒错了人(比如唤醒了一个还在等"队列满"的生产者,而实际应该唤醒等"队列空"的消费者)。- 只能配合
synchronized使用,灵活性差。
3.4 第四代:Condition(JDK 1.5 J.U.C包)
Doug Lea在 java.util.concurrent.locks 包里引入了 Condition 接口,配合 ReentrantLock 使用。它解决了Object监视器的最大痛点:一把锁可以创建多个Condition对象,每个Condition有独立的等待队列。
这意味着什么?你可以为"队列不满"建一个 notFull 队列,为"队列不空"建一个 notEmpty 队列。生产者满了就在 notFull 上等,消费者空了就在 notEmpty 上等。生产者生产完只唤醒 notEmpty 上的消费者,消费者消费完只唤醒 notFull 上的生产者。精准唤醒,不误伤。
3.5 第五代:BlockingQueue等高级工具
到这一步,JUC直接把等待通知机制封装成了开箱即用的工具类:
ArrayBlockingQueue/LinkedBlockingQueue:内部就是用Condition实现的生产者消费者队列。LockSupport.park()/unpark():更底层的许可式等待,不需要锁对象也能用。CountDownLatch/CyclicBarrier/Semaphore:各种场景化的同步器。
日常业务开发中,90%的场景直接用 BlockingQueue 就够了。但面试和底层中间件开发中,wait/notify 和 Condition 才是基本功。
四、怎么用:从基础语法到企业级实战代码
4.1 wait/notify 基础四板斧
使用 wait/notify 有四个铁律,违反任何一个都会出bug:
- 必须在synchronized块内调用 :
wait/notify必须持有对象监视器,否则抛IllegalMonitorStateException。 - 等待条件必须用while循环判断,不能用if:因为存在"虚假唤醒"(Spurious Wakeup),线程可能在没有被notify的情况下自己醒过来。
- notify()唤醒一个,notifyAll()唤醒全部 :生产环境优先用
notifyAll(),避免唤醒错对象导致永久等待。 - 等待和唤醒的对象必须是同一个监视器:在obj上wait,就必须在obj上notify。
基础示例代码:
java
public class WaitNotifyDemo {
private static final Object LOCK = new Object();
private static boolean hasData = false;
public static void main(String[] args) throws InterruptedException {
// 等待线程
Thread waiter = new Thread(() -> {
synchronized (LOCK) {
while (!hasData) { // 必须while,不能if
try {
System.out.println(Thread.currentThread().getName() + " 开始等待");
LOCK.wait(); // 释放锁,进入Wait Set
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + " 被唤醒,继续执行");
}
}, "Waiter-Thread");
// 通知线程
Thread notifier = new Thread(() -> {
synchronized (LOCK) {
hasData = true;
System.out.println(Thread.currentThread().getName() + " 改了状态,准备通知");
LOCK.notifyAll(); // 唤醒所有等待在LOCK上的线程
}
}, "Notifier-Thread");
waiter.start();
Thread.sleep(1000); // 确保waiter先进入等待
notifier.start();
}
}
运行输出顺序:
text
Waiter-Thread 开始等待
Notifier-Thread 改了状态,准备通知
Waiter-Thread 被唤醒,继续执行
4.2 经典生产者消费者:wait/notify版本
这是面试必考、生产必用的模型。我们用一个固定大小的缓冲区来演示:
java
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumerWaitNotify {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 5;
private final Object lock = new Object();
// 生产者
public void produce(int data) throws InterruptedException {
synchronized (lock) {
// 队列满了就等,用while防止虚假唤醒
while (queue.size() == MAX_SIZE) {
System.out.println("队列已满,生产者等待: " + data);
lock.wait();
}
queue.offer(data);
System.out.println("生产: " + data + ",当前队列: " + queue.size());
lock.notifyAll(); // 通知消费者
}
}
// 消费者
public int consume() throws InterruptedException {
synchronized (lock) {
// 队列空了就等
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待");
lock.wait();
}
int data = queue.poll();
System.out.println("消费: " + data + ",当前队列: " + queue.size());
lock.notifyAll(); // 通知生产者
return data;
}
}
public static void main(String[] args) {
ProducerConsumerWaitNotify pc = new ProducerConsumerWaitNotify();
// 3个生产者
for (int i = 0; i < 3; i++) {
final int id = i;
new Thread(() -> {
try {
for (int j = 0; j < 10; j++) {
pc.produce(id * 100 + j);
Thread.sleep(10);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Producer-" + i).start();
}
// 2个消费者
for (int i = 0; i < 2; i++) {
new Thread(() -> {
try {
while (true) {
pc.consume();
Thread.sleep(50);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Consumer-" + i).start();
}
}
}
注意这里用的是 notifyAll() 而不是 notify()。原因是:如果只调用 notify(),在多生产者多消费者场景下,可能唤醒的是另一个生产者而不是消费者,导致队列满了之后没有消费者被唤醒,所有生产者都在等待,死锁。
4.3 Condition版本:精准唤醒,企业级首选
Condition 是生产环境推荐的写法,核心优势是多个条件队列,精准唤醒。

java
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerCondition {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 5;
private final Lock lock = new ReentrantLock();
// 两个独立的条件队列
private final Condition notFull = lock.newCondition(); // 生产者等待:队列不满
private final Condition notEmpty = lock.newCondition(); // 消费者等待:队列不空
public void produce(int data) throws InterruptedException {
lock.lock();
try {
while (queue.size() == MAX_SIZE) {
System.out.println("队列满,生产者在notFull等待: " + data);
notFull.await(); // 生产者去notFull队列排队
}
queue.offer(data);
System.out.println("生产: " + data + ",队列大小: " + queue.size());
notEmpty.signal(); // 精准唤醒一个消费者
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
System.out.println("队列空,消费者在notEmpty等待");
notEmpty.await(); // 消费者去notEmpty队列排队
}
int data = queue.poll();
System.out.println("消费: " + data + ",队列大小: " + queue.size());
notFull.signal(); // 精准唤醒一个生产者
return data;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ProducerConsumerCondition pc = new ProducerConsumerCondition();
// 测试代码同4.2,省略
}
}
对比 wait/notify 版本,Condition版本的优势一目了然:
- 生产者满了只在
notFull上等,消费者消费完只signal()一个在notFull上的生产者,不会误唤醒其他生产者。 - 消费者空了只在
notEmpty上等,生产者生产完只signal()一个在notEmpty上的消费者,不会误唤醒其他消费者。 - 性能更高:减少了无效的锁竞争和线程上下文切换。
4.4 企业项目实战:订单异步处理管道
下面这个案例来自一个真实的电商订单系统。业务场景是:订单支付成功后,需要异步执行"扣库存、发优惠券、发短信通知、写积分流水"四个动作。用 ArrayBlockingQueue 作为任务缓冲队列,用Condition的思想来解耦生产和消费:
java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// 订单任务模型
class OrderTask {
private final String orderId;
private final String type; // DEDUCT_STOCK / SEND_COUPON / SEND_SMS / ADD_POINT
public OrderTask(String orderId, String type) {
this.orderId = orderId;
this.type = type;
}
public String getOrderId() { return orderId; }
public String getType() { return type; }
}
// 订单处理管道:核心就是一个阻塞队列 + 消费线程池
public class OrderPipeline {
// ArrayBlockingQueue内部就是用ReentrantLock + Condition实现的
private final BlockingQueue<OrderTask> taskQueue = new ArrayBlockingQueue<>(1000);
// 启动消费线程池
public void startConsumers(int threadCount) {
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(() -> {
while (true) {
try {
// take()内部就是notEmpty.await(),队列空时自动挂起
OrderTask task = taskQueue.take();
handleTask(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "Order-Consumer-" + i);
t.setDaemon(true);
t.start();
}
}
// 支付成功后调用,往队列扔任务
public void submitOrder(String orderId) throws InterruptedException {
// put()内部就是notFull.await(),队列满时自动阻塞
taskQueue.put(new OrderTask(orderId, "DEDUCT_STOCK"));
taskQueue.put(new OrderTask(orderId, "SEND_COUPON"));
taskQueue.put(new OrderTask(orderId, "SEND_SMS"));
taskQueue.put(new OrderTask(orderId, "ADD_POINT"));
}
private void handleTask(OrderTask task) {
switch (task.getType()) {
case "DEDUCT_STOCK" -> System.out.println("扣库存: " + task.getOrderId());
case "SEND_COUPON" -> System.out.println("发优惠券: " + task.getOrderId());
case "SEND_SMS" -> System.out.println("发短信: " + task.getOrderId());
case "ADD_POINT" -> System.out.println("加积分: " + task.getOrderId());
}
}
public static void main(String[] args) throws InterruptedException {
OrderPipeline pipeline = new OrderPipeline();
pipeline.startConsumers(4); // 4个消费线程
// 模拟10笔订单支付成功
for (int i = 1; i <= 10; i++) {
pipeline.submitOrder("ORD20261103" + i);
System.out.println("订单提交: ORD20261103" + i);
}
}
}
这段代码背后就是 Condition 等待通知机制的工业级封装。你不需要手写 await/signal,但你必须知道它在底层帮你做了什么。
五、主流方案横向对比:wait/notify、Condition、LockSupport、BlockingQueue
| 对比维度 | Object.wait/notify | Condition (ReentrantLock) | LockSupport.park/unpark | BlockingQueue |
|---|---|---|---|---|
| 所属层次 | JVM内置监视器 | J.U.C并发包 | J.U.C并发包底层 | J.U.C高级工具 |
| 锁依赖 | 必须配合synchronized | 必须配合ReentrantLock | 不需要锁对象 | 内部封装了Lock+Condition |
| 条件队列数量 | 每个对象只有一个Wait Set | 一把Lock可建多个Condition | 每个线程一个许可 | 内部内置notFull/notEmpty两个 |
| 精准唤醒 | notify()随机唤醒一个,容易唤醒错 | 可按Condition精准唤醒对应队列 | unpark指定线程,最精准 | 自动精准唤醒 |
| 超时等待 | wait(timeout)支持 | await(time, unit)支持 | parkNanos/parkUntil支持 | offer/poll带超时 |
| 可中断 | 响应InterruptedException | 响应InterruptedException | 响应中断(需手动检查) | 响应中断 |
| 释放锁时机 | wait()自动释放 | await()自动释放 | 不涉及锁 | 内部管理 |
| 使用复杂度 | 低,但坑多 | 中等,灵活 | 低,最底层 | 极低,开箱即用 |
| 性能 | 一般(JVM监视器实现) | 高(AQS实现) | 最高(直接操作线程状态) | 高(基于Condition) |
| 典型用途 | 简单的线程协作 | 生产者消费者、自定义同步器 | 锁、并发工具的底层实现 | 业务中的任务队列、线程池 |
| 生产推荐度 | 老代码维护用,新代码少用 | 自定义中间件/框架首选 | 不直接业务用,了解即可 | 业务开发首选 |
选型建议:
- 业务开发直接用
BlockingQueue(ArrayBlockingQueue/LinkedBlockingQueue),别自己造轮子。 - 需要自定义同步器(比如自己写一个有界队列),用
ReentrantLock + Condition。 - 维护老项目遇到
synchronized + wait/notify,能跑就别动,重构时再迁到JUC。 LockSupport.park/unpark是JUC的底层基石,面试要知道,但业务代码几乎不直接用。
六、常用场景盘点
等待-通知机制在企业项目中的使用场景远不止生产者消费者,以下几个是高频出现的:
场景1:异步任务队列解耦
订单系统、消息推送系统、日志收集系统,几乎所有需要"生产端快速响应、消费端慢慢处理"的场景,核心都是BlockingQueue+Condition。
场景2:线程池工作队列
ThreadPoolExecutor 内部的工作队列就是一个BlockingQueue。核心线程不够时,任务进队列等待,有空闲线程时被唤醒取任务。你每天用的线程池,底层就是等待通知机制。
场景3:CountDownLatch等待多任务完成
主线程启动10个子任务后等待全部完成,子任务每完成一个countDown一次,主线程await直到计数归零。内部就是AQS+Condition。
场景4:同步屏障CyclicBarrier
多组线程互相等待,直到所有线程都到达屏障点才一起继续执行。典型用法是多线程分治计算(比如并行统计、矩阵运算)。
场景5:数据库连接池
连接池满了,新获取连接的线程在Condition队列上等待;有连接释放时,signal唤醒一个等待者。Druid、HikariCP内部都是这个模式。
场景6:读写锁的读共享/写互斥
ReentrantReadWriteLock 内部为读线程和写线程分别维护了等待队列,用Condition实现"读多了写要等、写进来了读要等"的协作逻辑。
七、面试官高频面试题(带答案要点)
面试题1:wait()和sleep()的区别?
这是最高频的一题,标准答案分四点:
- 所属类不同 :
wait()是Object类的方法,sleep()是Thread类的静态方法。 - 释放锁不同 :
wait()会释放持有的对象锁,sleep()不释放任何锁,抱着锁睡。 - 使用位置不同 :
wait()必须在synchronized块中调用,否则抛IllegalMonitorStateException;sleep()随处可用。 - 唤醒时机不同 :
wait()需要被notify/notifyAll唤醒,或者超时自动返回;sleep()时间到了自动苏醒。 - 用途不同 :
wait()是线程间协作,sleep()只是单纯暂停当前执行。
面试题2:为什么wait()/notify()必须在synchronized块中调用?
因为wait/notify操作的是对象监视器状态,而监视器的获取和释放必须是原子的。如果不在同步块里,可能出现:线程刚判断完条件不满足要wait,还没来得及挂起,另一个线程已经改了条件并notify了,这个notify就丢了,线程永远等下去。synchronized保证了"检查条件->等待挂起"这个过程的原子性。
面试题3:为什么等待条件要用while循环而不是if?
因为虚假唤醒(Spurious Wakeup)。在JVM实现中,线程可能在没有任何线程调用notify的情况下从wait()返回(这是操作系统层面允许的行为,POSIX标准明确允许)。如果用if判断,醒来后不会重新检查条件,直接往下走,此时条件可能根本不满足。用while循环,醒来后会再检查一次条件,不满足就继续wait。这是Doug Lea在《Java并发编程实战》中明确要求的范式。
面试题4:notify()和notifyAll()的区别,生产环境用哪个?
notify():随机唤醒Wait Set中任意一个线程,该线程重新竞争锁。notifyAll():唤醒Wait Set中所有线程,所有线程重新竞争锁,只有一个能拿到锁执行,其余继续等待。
生产环境优先用notifyAll()。原因是notify()在多条件、多生产者多消费者场景下,可能唤醒错误类型的线程(比如生产者唤醒了生产者,而不是消费者),导致条件满足时没有线程被正确唤醒,最终死锁。虽然notifyAll()有性能开销(惊群效应),但正确性优先。Condition的signal()在明确知道只有一个等待者需要被唤醒时,才可以安全使用。
面试题5:Condition比Object的wait/notify强在哪里?
三个核心优势:
- 多条件队列:一把锁可以创建多个Condition,不同条件的等待者在不同队列,精准唤醒不干扰。
- 丰富的等待方式:支持awaitUninterruptibly(不响应中断)、awaitUntil(截止时间)、awaitNanos(纳秒级超时)。
- 公平性可选:ReentrantLock支持公平锁模式,Condition配合公平锁可以实现先到先得的唤醒顺序。
面试题6:什么是虚假唤醒?怎么避免?
虚假唤醒指的是:线程在没有被其他线程显式调用signal/notify的情况下,从等待状态返回。这不是bug,而是底层操作系统出于性能和历史兼容性考虑允许的行为。
避免方式就是:永远用while循环检查等待条件,不要用if。醒来后重新检查条件,条件不满足就继续等待。这是Java官方文档明确推荐的写法。
面试题7:LockSupport.park()和wait()有什么区别?
- 不需要持有锁对象,不会抛IllegalMonitorStateException。
- 可以先unpark再park:unpark会给线程发一个许可,之后park直接返回,不会像notify那样"提前发通知就丢了"。
- park响应中断但不抛异常,只是静默返回,需要自己检查Thread.interrupted()。
- park是Lock、ReentrantLock、并发容器的底层实现基础。
面试题8:BlockingQueue的底层原理是什么?
ArrayBlockingQueue 内部就是一个ReentrantLock + 两个Condition(notFull、notEmpty)+ 一个数组。put()时队列满就在notFull上await,take()时队列空就在notEmpty上await。put成功后signal notEmpty上的消费者,take成功后signal notFull上的生产者。这就是Condition等待通知机制的教科书级工业实现。
八、总结
等待-通知机制解决的核心问题只有一个:让线程在条件不满足时高效地等待,在条件满足时及时地被唤醒。
从演进路径看,我们走过了死循环忙等(CPU灾难)、sleep轮询(延迟不可控)、wait/notify(JVM内置但只有一个等待队列)、Condition(多条件队列精准唤醒)、BlockingQueue(开箱即用)这五代。每一代都是在解决上一代的痛点。
在实际工程中:
- 业务代码优先用
BlockingQueue、ThreadPoolExecutor这些高级工具,它们已经把等待通知机制封装好了。 - 写自定义并发组件时,用
ReentrantLock + Condition,多条件队列精准唤醒。 - 维护老代码时,看到
synchronized + wait/notify,记住while循环检查条件、优先notifyAll、同对象监视器这三条铁律。 - 面试时,把wait vs sleep、while vs if、notify vs notifyAll、Condition vs wait/notify这四个问题答透,基本就稳了。
并发编程的世界里,所有性能问题最终都归结为两件事:减少不必要的竞争,减少不必要的等待。等待-通知机制就是后者的标准答案。
转载声明
本文为原创文章,如需转载,请联系作者获得授权,并注明出处。