个人观点,仅供参考!
作为刚入门 Java 并发编程的小伙伴,你是不是也曾对其抓耳挠腮?明明想让线程 “等条件满足再干活”,结果要么唤醒错对象,要么线程稀里糊涂被叫醒?今天咱们聊的Contidion接口,就是解决这个痛点的 “并发协作神器”——只有满足指定条件,线程才会被精准唤醒干活
一、先搞懂Condition 到底是个啥?
Condition 是 Java 并发编程里的 “高级等待 / 唤醒工具”,其实就是一个“条件等待器”,在“拿着锁”的前提下(因为Condition依赖 Lock 锁实现),规定线程“什么时候该等、谁来叫醒”的一套规则。它比传统的 synchronized + wait ()/notify () 更灵活。
先不要急着看源码,我们先从奶茶店点单场景入手:
二、Condition 的 工作流程,像极了奶茶店点餐到取餐
1、奶茶店的"取号规则":没锁就没资格等
你去一家网红奶茶店点单,店里有个明确规定,必须先排队取号(拿到锁 Lock),才能进入后续流程。这对应并发编程里的核心约束:Condition 必须依赖 Lock 存在。没拿号(没锁)的人,连"等奶茶"的资格都没有——直接进店喊"我的奶茶呢",只会被保安请出去(抛出 IllegalMonitorStateException)。
2、等待逻辑:条件不满足,找个座位歇着
点完单后,你拿到取餐码 "32 号",此时奶茶还没做好(条件不满足)。你总不能堵在柜台前催单吧?那样会影响后面的人点单。于是店员指引你去 30-39 号等待区 坐着等吧。
这个过程对应 await()的三个关键动作:
| 动作 | 奶茶店场景 | 线程行为 |
| 进入等待区 | 你坐到 30-39 号区域 | 线程进入 Condition 的等待队列 |
| 交出排队号 | 你的取号牌暂时失效 | 释放 Lock 锁 |
| 等待 | 休息 | 线程阻塞,不消耗 CPU |
这就是 Condition "等待",条件不满足时,让线程暂时"歇着",还不耽误别人干活。
3 、唤醒逻辑:条件满足,精准叫号
当店员喊32 号取餐你听到后起身,但注意——不是直接拿到奶茶,而是:

这个过程就对应signal() 的行为。注意:signal() 是"叫你起来排队",不是"瞬移到柜台完成取餐"。被唤醒 ≠ 立刻执行,还得重新抢锁!
三、与传统 wait()和notify()比较
1、传统的synchronized + wait()和notify()就像一家简陋奶茶店,只有一个等待区,不管你是普通订单、加料订单还是外卖订单,都挤在一起。店员做好一杯奶茶,只能 随便喊一个人notify()或者notifyAll()所有人.
2、 而 Condition按条件分等待区,做好哪类订单,就喊哪个区。唤醒精准,无效唤醒降到最低。
| 特性 | synchronized + wait/notify | Lock + Condition |
| 等待队列数量 | 只有 1 个(所有线程混在一起) |
可创建多个(按条件分队列) |
| 唤醒精度 | 随机唤醒 1 个 / 唤醒所有 | 精准唤醒指定条件的线程 |
| 使用灵活性 | 低(依赖对象监视器) | 高(可搭配 Lock 灵活控制) |
四、关键细节,新手必避坑
1、必须"循环检查条件"。
在此之前我们要先了解虚假唤醒,比如奶茶店的喊号机故障,没做好你的奶茶,却误喊了 “32 号取餐”;或者店员喊的是 “22 号”,你听岔了。这就是并发里的 虚假唤醒 —— 线程没被 signal () 唤醒,也可能自己醒过来(JVM 底层机制导致,比如系统中断、线程调度)。
①if检查,踩坑的错误写法
if (milkTeas.size() == 0) {
System.out.println("奶茶卖光了,顾客等一等~");
notEmpty.await();
}
②while循环,安全的正确写法
while (milkTeas.size() == 0) {
System.out.println("奶茶卖光了,顾客等一等~");
notEmpty.await();
}
2、condition必须在 Lock 保护下调用
必须在Lock保护下调用,没lock()就调await()和signal(),会直接抛IlegalMonitorStateException的错误,记住“先拿号,再等叫号”!
3、死锁风险
唤醒线程前一定要修改条件!比如店员喊了取餐码,结果奶茶还没做好,顾客过来又得等,可能导致所有线程都卡住。
4、别漏了 finally 释放锁
不管线程是正常执行还是抛异常,都要在finally里lock.unlock()。
5、 lock() 通常放在 try 外面
因为原始异常(lock()抛出的,比如线程被中断、JVM 内部错误)会被finally中的异常覆盖,这样会导致 “拿锁失败” 时,也执行 unlock (),触发致命异常。记住只有 成功拿到锁”的线程,才有资格释放锁。
五、典型场景Condition 实现 “生产者 - 消费者”
光看理论还不够,我们继续用 “奶茶店” 场景实战一把。彻底搞懂 Condition 怎么解决 “制作员做奶茶、顾客买奶茶” 的协作问题 —— 奶茶库存满了制作员就得等,库存空了顾客就得等,用 Condition 精准唤醒对应角色。
import java.util.LinkedList;
import java.util.concurrent.locks.*;
// 奶茶店(最多5杯库存,新手易懂版)
public class MilkTeaShop {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 库存未满
private final Condition notEmpty = lock.newCondition(); // 库存非空
private final LinkedList<String> milkTeas = new LinkedList<>();
private static final int MAX = 5; // 最大库存5杯
// 制作奶茶(生产者)
public void makeTea(String tea) throws InterruptedException {
lock.lock();
try {
while (milkTeas.size() == MAX) {
System.out.println("奶茶满了,制作员等一等~");
notFull.await();
}
milkTeas.add(tea);
System.out.println("做好:" + tea + ",库存:" + milkTeas.size());
notEmpty.signal(); // 喊顾客来买
} finally { lock.unlock(); }
}
// 购买奶茶(消费者)
public String buyTea() throws InterruptedException {
lock.lock();
try {
while (milkTeas.size() == 0) {
System.out.println("奶茶卖光了,顾客等一等~");
notEmpty.await();
}
String tea = milkTeas.removeFirst();
System.out.println("买走:" + tea + ",库存:" + milkTeas.size());
notFull.signal(); // 喊制作员做茶
return tea;
} finally { lock.unlock(); }
}
//1制作员+2顾客
public static void main(String[] args) {
MilkTeaShop shop = new MilkTeaShop();
// 制作员:做8杯珍珠奶茶
new Thread(() -> {
for (int i = 1; i <= 8; i++) {
try { shop.makeTea("珍珠奶茶" + i); Thread.sleep(500); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}).start();
// 顾客A:买4杯
new Thread(() -> {
for (int i = 1; i <= 4; i++) {
try { shop.buyTea(); Thread.sleep(800); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}).start();
// 顾客B:买4杯
new Thread(() -> {
for (int i = 1; i <= 4; i++) {
try { shop.buyTea(); Thread.sleep(1000); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}).start();
}
}
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐加关注🔔,感谢你的阅读
3242

被折叠的 条评论
为什么被折叠?



