【016】wait () 和 Condition 的区别
🚀 线程协作优化:Condition精准唤醒替代wait() 摘要 传统wait()/notifyAll()在多条件线程协作中存在"广播式唤醒"问题,导致资源浪费和逻辑混乱。本文通过餐厅点餐系统案例,对比展示了Condition的精准唤醒优势: 问题分析:使用wait()时,堂食和外卖订单共用一个等待队列,导致唤醒不区分订单类型,引发优先级错乱 ....
文章目录
🚀 用 wait () 做点餐系统被骂后,Condition 教我给线程 “精准喊号”!
零、引入

你刚用 wait () 搞定外卖催单功能,就飘了 —— 主动揽下公司食堂的 “智能点餐系统” 开发,拍胸脯说 “线程协作稳如老狗”。结果上线第一天就炸了:堂食顾客点的 “番茄炒蛋” 等了 20 分钟还没上,外卖订单却源源不断地被厨师优先做,顾客拍着桌子骂 “系统歧视堂食”,领导把你叫到办公室,桌子拍得比 JVM GC 还响:“3 小时内修复,不然这个月全勤奖没了!”
你急得满头大汗,查代码才发现:用 wait () 和 notifyAll () 实现的 “订单唤醒”,不管是堂食还是外卖订单,唤醒时全堆到厨师面前,厨师随手拿起哪个做哪个,完全没章法。就在你准备把 “离职申请.pdf” 改成 “全勤奖申诉.pdf” 时,王哥叼着刚买的煎饼果子凑过来:“慌啥?wait () 这‘广播喇叭’哪能搞定多条件等待?用 Condition 给线程‘分组喊号’啊,比你这‘瞎唤醒’靠谱 10 倍!”
一、先吐槽 wait ():多条件场景下就是 “添乱神器”

王哥抹了把嘴角的葱花,指着你的代码笑出了抬头纹:“你把堂食和外卖订单放一个队列,用同一个锁和 wait (),就像餐厅里只有一个广播喇叭 —— 不管是喊堂食顾客取餐,还是喊外卖员来拿单,都对着所有人喊‘有人取餐’,结果堂食顾客跑过来发现是外卖单,外卖员跑过来发现是堂食单,不乱才怪!”
你的错误代码(wait () 瞎唤醒版)
public class BadCanteenDemo {
// 锁对象:整个餐厅一个锁
static final Object LOCK = new Object();
// 堂食订单队列
static Queue<String> dineInOrders = new LinkedList<>();
// 外卖订单队列
static Queue<String> takeoutOrders = new LinkedList<>();
public static void main(String[] args) {
// 厨师线程:负责做订单
new Thread(() -> {
while (true) {
synchronized (LOCK) {
// 两个队列都空就等
while (dineInOrders.isEmpty() && takeoutOrders.isEmpty()) {
try {
System.out.println("厨师:暂无订单,歇会儿...");
LOCK.wait(); // 等待订单通知
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 随便拿一个订单做(问题核心:不分堂食外卖)
if (!dineInOrders.isEmpty()) {
String order = dineInOrders.poll();
System.out.println("厨师:做堂食订单——" + order);
} else {
String order = takeoutOrders.poll();
System.out.println("厨师:做外卖订单——" + order);
}
}
}
}, "厨师").start();
// 堂食下单线程(顾客1)
new Thread(() -> {
synchronized (LOCK) {
dineInOrders.add("番茄炒蛋+米饭");
System.out.println("顾客1:点了堂食——番茄炒蛋+米饭");
LOCK.notifyAll(); // 喊厨师:有订单了!(不分类型)
}
}, "堂食顾客1").start();
// 外卖下单线程(外卖员1)
new Thread(() -> {
synchronized (LOCK) {
takeoutOrders.add("黄焖鸡米饭+可乐");
System.out.println("外卖员1:点了外卖——黄焖鸡米饭+可乐");
LOCK.notifyAll(); // 同样喊厨师:有订单了!
}
}, "外卖员1").start();
}
}
跑起来可能出现的离谱日志:
厨师:暂无订单,歇会儿...
顾客1:点了堂食——番茄炒蛋+米饭
外卖员1:点了外卖——黄焖鸡米饭+可乐
厨师:做外卖订单——黄焖鸡米饭+可乐
“你看,堂食先下单,结果厨师先做了外卖,” 王哥拍桌,“wait () 的问题就在于:一个锁只能对应一个‘等待队列’,notifyAll () 会唤醒所有等这个锁的线程,不管它们等的是啥条件 —— 就像你在公司群发‘来个人帮忙’,不管是做设计的、写代码的、搞运维的,全跑过来,最后只有一个人能干活,其他人白跑一趟,效率低还乱!”
插个冷笑话:“wait () 的唤醒逻辑,就像你在群里 @所有人问‘谁会修电脑’,结果来了个厨师、一个司机、一个保洁,只有一个 IT 能修 —— 不是说 @所有人不对,是场景不对,多条件等待时,wait () 就是‘一刀切’的笨办法!”
二、Condition:给线程 “分群喊号” 的智能对讲机

王哥把煎饼果子放一边,打开 IDE:“Condition 是 Lock 的‘配套工具’,相当于给每个条件装了个‘专属对讲机’—— 堂食订单用一个对讲机,外卖订单用另一个,喊堂食的时候只有堂食相关的线程能听见,喊外卖的时候只有外卖相关的能听见,精准高效!”
2.1 先搞懂 Condition 的核心优势(对比 wait ())
- 多条件分组:一个 Lock 可以创建多个 Condition,每个 Condition 对应一个等待队列 —— 比如餐厅一个 Lock,堂食 Condition 和外卖 Condition,分开等待、分开唤醒;
- 精准唤醒:可以用signal()唤醒对应条件队列里的一个线程,或signalAll()唤醒对应队列里的所有线程,不会唤醒无关线程;
- 更灵活的等待:支持超时等待、可中断等待,还能判断当前 Condition 上有多少线程在等(getWaitQueueLength()),比 wait () 功能强太多。
2.2 示例代码
修复后的代码(Condition 精准唤醒版)
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 GoodCanteenDemo {
// 可重入锁:替代synchronized(功能更强)
static final Lock LOCK = new ReentrantLock();
// 堂食专属Condition(对讲机1):负责堂食订单的等待和唤醒
static final Condition DINE_IN_COND = LOCK.newCondition();
// 外卖专属Condition(对讲机2):负责外卖订单的等待和唤醒
static final Condition TAKEOUT_COND = LOCK.newCondition();
// 堂食订单队列
static Queue<String> dineInOrders = new LinkedList<>();
// 外卖订单队列
static Queue<String> takeoutOrders = new LinkedList<>();
public static void main(String[] args) {
// 厨师线程:现在能精准处理不同订单了
new Thread(() -> {
while (true) {
LOCK.lock(); // 替代synchronized,手动加锁
try {
// 先检查堂食订单(假设堂食优先级高)
while (dineInOrders.isEmpty()) {
// 堂食没订单,就等堂食的Condition通知
System.out.println("厨师:暂无堂食订单,等堂食顾客下单...");
DINE_IN_COND.await(); // 对应wait(),但只等堂食的通知
}
// 处理堂食订单
String dineInOrder = dineInOrders.poll();
System.out.println("厨师:做堂食订单——" + dineInOrder);
// 处理完堂食,再检查外卖订单
while (takeoutOrders.isEmpty()) {
System.out.println("厨师:暂无外卖订单,等外卖员下单...");
TAKEOUT_COND.await(); // 等外卖的通知
}
// 处理外卖订单
String takeoutOrder = takeoutOrders.poll();
System.out.println("厨师:做外卖订单——" + takeoutOrder);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LOCK.unlock(); // 手动解锁,必须放finally
}
}
}, "厨师").start();
// 堂食下单线程(顾客1)
new Thread(() -> {
LOCK.lock();
try {
dineInOrders.add("番茄炒蛋+米饭");
System.out.println("顾客1:点了堂食——番茄炒蛋+米饭");
// 只唤醒堂食对应的Condition,不会打扰外卖的等待
DINE_IN_COND.signal(); // 对应notify(),精准喊堂食相关线程
} finally {
LOCK.unlock();
}
}, "堂食顾客1").start();
// 外卖下单线程(外卖员1)
new Thread(() -> {
LOCK.lock();
try {
takeoutOrders.add("黄焖鸡米饭+可乐");
System.out.println("外卖员1:点了外卖——黄焖鸡米饭+可乐");
// 只唤醒外卖对应的Condition
TAKEOUT_COND.signal(); // 精准喊外卖相关线程
} finally {
LOCK.unlock();
}
}, "外卖员1").start();
}
}
运行结果(精准有序):
厨师:暂无堂食订单,等堂食顾客下单...
顾客1:点了堂食——番茄炒蛋+米饭
厨师:做堂食订单——番茄炒蛋+米饭
厨师:暂无外卖订单,等外卖员下单...
外卖员1:点了外卖——黄焖鸡米饭+可乐
厨师:做外卖订单——黄焖鸡米饭+可乐
“你看,现在堂食先下单先做,外卖后下单后做,再也不乱了!” 王哥得意,“Condition 就像给线程分了‘部门’,每个部门有专属对讲机,喊人的时候只喊自己部门的,不会让其他部门的人白跑 —— 这效率,比 wait () 的‘广播喇叭’高多了!”
三、wait () vs Condition:核心区别大对比

通俗比喻:
- wait() :学校广播室只有一个喇叭,老师喊 “请班长来办公室”,所有班的班长都得跑到广播室门口,最后发现只喊 1 班班长 —— 其他班长白跑一趟,还耽误自己干活;
- Condition :每个班都有专属对讲机,老师喊 “1 班班长来办公室”,只有 1 班班长能听见,直接过去就行,其他班长该干嘛干嘛,效率拉满。
“再举个代码里的例子,” 王哥补充,“如果你用 wait () 做一个‘生产者 - 消费者’,生产者分‘水果生产者’和‘蔬菜生产者’,消费者分‘水果消费者’和‘蔬菜消费者’,wait () 唤醒时会让水果消费者跑到蔬菜队列前,蔬菜消费者跑到水果队列前 —— 纯属添乱;但 Condition 可以给水果和蔬菜各建一个 Condition,精准唤醒,绝不会串场!”
四、使用场景:什么时候用 wait (),什么时候用 Condition?

王哥喝了口冰阔落,总结得明明白白:
4.1 用 wait () 的场景:简单单条件等待
比如之前的外卖催单(只有 “接单” 一个条件)、简单的线程同步(A 线程等 B 线程完成)—— 场景单一,不需要分组唤醒,用 wait () 足够了,代码简单,不用手动加锁解锁(synchronized 隐式处理)。
“就像你只需要喊‘所有人等我’,用广播喇叭(wait ())就行,没必要搞一堆对讲机。”
4.2 用 Condition 的场景:多条件分组等待
- 多类型订单处理(堂食 / 外卖、普通订单 / VIP 订单);
- 复杂生产者 - 消费者(多生产者多消费者,按商品类型分组);
- 需要精准唤醒的场景(比如线程池里按任务优先级唤醒线程);
- 需要获取等待队列长度(比如统计当前有多少个外卖订单在等)。
“这些场景用 wait () 会乱成一锅粥,用 Condition 就像给线程‘分了班’,管理起来清清楚楚 —— 这就是‘专业’和‘凑活’的区别!”
插个总结:“很多程序员分不清这俩,就像分不清‘一次性筷子’和‘可降解分餐筷’—— 都是筷子,但场景不同:简单吃饭用一次性的(wait ()),多人分餐、讲究卫生用分餐筷(Condition);用 wait () 处理多条件,就像用一次性筷子给 10 个人分菜,又乱又不卫生!”
五、总结:3 句口诀 + 避坑指南
王哥把代码保存好,给你划重点:
- 单条件用 wait (),多条件用 Condition:场景越简单,越不用复杂工具;
- Condition 要配 Lock 用:别想着用 Condition 搭配 synchronized,就像用对讲机搭配广播喇叭,不兼容;
- Lock 必须手动解锁:Condition 的 await () 不会自动解锁,一定要把 unlock () 放进 finally,不然锁会一直被占用,线程全堵死 —— 这比 wait () 忘写在同步块里还惨!
5.1 避坑指南(王哥的血泪史)

“我刚用 Condition 的时候,把 unlock () 写在了 try 块里,” 王哥捂脸,“结果代码抛异常,unlock () 没执行,锁一直被占着,所有线程都堵死了,我以为系统崩了,还重启了服务器,被领导骂‘连锁都不会解’—— 现在我写 Condition,先写 lock () 和 finally 里的 unlock (),再写中间的逻辑,比摸鱼还熟练!”
六、最后说句实在的
Condition 专业。今天这两篇代码你复制过去就能跑,改改 Condition 的数量和唤醒逻辑,就能用到自己的项目里。
要是你搞懂了,别光顾着自己爽,点赞让更多人避坑,关注我!
对了,把这篇分享给你那用 wait () 处理多条件等待、代码乱成一锅粥的同事,下次代码 review 时,你就能笑着说:“兄弟,别用广播喇叭喊人了,给线程配个专属对讲机(Condition),让它们精准干活呗!”





更多推荐



所有评论(0)