Java线程协作实战:wait/notify+Condition精准通信,吃透生产者-消费者模型(附面试避坑)
本文深入讲解Java线程通信的两种核心方案:1)synchronized+wait/notify基础版协作,通过生产者-消费者模型演示,强调必须使用while循环避免虚假唤醒;2)Lock+Condition进阶方案,实现精准通知,提高多线程协作效率。文章包含实战案例(订单配送系统)、面试高频问题对比(wait/notify vs Condition)、常见避坑指南和拓展练习。配套源码和资料可通过
本文配套源码、多线程思维导图已整理至GZH「咖啡Java研习室」,回复【学习资料】即可领取;系列文章持续更新,关注不迷路~
目录
-
为什么线程通信是面试高频?(痛点直击)
-
方案1:synchronized+wait/notify(基础版协作)
-
方案2:Lock+Condition(精准通知,面试重点)
-
两种方案核心对比(表格速记)
-
实战:订单配送协作系统(集合+IO+异常整合)
-
面试避坑&拓展练习
一、为什么线程通信是面试高频?(痛点直击)
多线程开发中,「线程同步」解决“抢资源不打架”,而「线程通信」解决“分工合作不内耗”——这俩是面试必问的核心组合!
举个反例:
-
生产者疯狂生产,仓库满了还不停→内存浪费
-
消费者无脑循环消费,仓库空了还在查→CPU空转
-
最终系统效率低、资源浪费,甚至出现数据错乱
而线程通信的核心就是「按需工作」:
✅ 仓库满→生产者暂停,等消费者通知
✅ 仓库空→消费者等待,等生产者通知
✅ 状态变了→精准唤醒,不做无效操作
今天就拆解两种核心通信方案,从基础到实战,再到面试踩坑,一次性吃透!
二、方案1:synchronized+wait/notify(基础版协作)
wait()、notify()、notifyAll()是Java内置通信方法,必须配合synchronized锁使用,调用对象是锁对象本身。
1. 核心方法解析(面试常问)
|
方法 |
作用说明 |
注意点 |
|
wait() |
释放锁+进入等待状态,需被notify()/notifyAll()唤醒或超时自动唤醒 |
必须处理InterruptedException;不能用if判断条件(下文避坑) |
|
notify() |
随机唤醒锁对象上1个等待线程,唤醒后需重新竞争锁 |
多生产者多消费者场景易导致“永久等待”,不推荐单用 |
|
notifyAll() |
唤醒锁对象上所有等待线程,避免漏唤醒 |
适合多角色协作,是生产环境首选 |
2. 实战:基础版生产者-消费者(集合+IO日志)
用ArrayList做仓库,实现“生产→存仓→消费”的基础协作,同时用IO记录商品流转日志(衔接Java IO知识点)。
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; // 商品实体 class Goods { private String id; private String name; public Goods(String id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "商品【" + id + "】" + name; } public String getId() { return id; } } // 仓库类:synchronized+wait/notify核心逻辑 class GoodsWarehouse { private static final int MAX_SIZE = 3; // 仓库最大容量 private List<Goods> warehouse = new ArrayList<>(); // 生产商品 public synchronized void produce(Goods goods) throws InterruptedException { // 🔴 面试坑:必须用while循环(而非if)避免虚假唤醒 while (warehouse.size() == MAX_SIZE) { System.out.println("仓库已满," + Thread.currentThread().getName() + " 进入等待"); this.wait(); // 释放锁,进入等待队列 } // 生产逻辑 warehouse.add(goods); System.out.println(Thread.currentThread().getName() + " 生产了" + goods + ",当前库存:" + warehouse.size()); writeLog("生产", goods); // 写入日志 this.notifyAll(); // 唤醒所有等待线程(生产者/消费者) } // 消费商品 public synchronized Goods consume() throws InterruptedException { while (warehouse.size() == 0) { System.out.println("仓库已空," + Thread.currentThread().getName() + " 进入等待"); this.wait(); } // 消费逻辑 Goods goods = warehouse.remove(0); System.out.println(Thread.currentThread().getName() + " 消费了" + goods + ",当前库存:" + warehouse.size()); writeLog("消费", goods); this.notifyAll(); // 唤醒生产者继续生产 return goods; } // IO日志记录(衔接Java IO知识点) private void writeLog(String type, Goods goods) { try (BufferedWriter bw = new BufferedWriter(new FileWriter("goods_log.txt", true))) { String log = Thread.currentThread().getName() + "|" + type + "|" + goods.getId() + "|" + System.currentTimeMillis() + "\n"; bw.write(log); } catch (IOException e) { System.out.println("日志写入失败"); e.printStackTrace(); } } } // 生产者任务 class Producer implements Runnable { private GoodsWarehouse warehouse; private int producerId; public Producer(GoodsWarehouse warehouse, int producerId) { this.warehouse = warehouse; this.producerId = producerId; } @Override public void run() { for (int i = 1; i <= 5; i++) { try { Goods goods = new Goods("P" + producerId + "-" + i, "零食" + i); warehouse.produce(goods); Thread.sleep(500); // 模拟生产耗时 } catch (InterruptedException e) { e.printStackTrace(); } } } } // 消费者任务 class Consumer implements Runnable { private GoodsWarehouse warehouse; public Consumer(GoodsWarehouse warehouse) { this.warehouse = warehouse; } @Override public void run() { for (int i = 1; i <= 8; i++) { try { warehouse.consume(); Thread.sleep(800); // 模拟消费耗时 } catch (InterruptedException e) { e.printStackTrace(); } } } } // 测试类 public class WaitNotifyDemo { public static void main(String[] args) { GoodsWarehouse warehouse = new GoodsWarehouse(); // 2个生产者+2个消费者 new Thread(new Producer(warehouse, 1), "生产者A").start(); new Thread(new Producer(warehouse, 2), "生产者B").start(); new Thread(new Consumer(warehouse), "消费者X").start(); new Thread(new Consumer(warehouse), "消费者Y").start(); } }
3. 面试避坑:为什么用while而非if判断等待条件?
👉 虚假唤醒问题:线程被notify()唤醒后,可能仓库状态已变(比如其他线程抢先生产/消费),若用if判断,唤醒后会直接执行后续代码,导致库存超标或空仓消费。
而while循环会在唤醒后重新校验条件,确保符合状态才继续执行——这是面试高频考点,记死!
三、方案2:Lock+Condition(精准通知,面试重点)
wait/notify的短板是「随机唤醒」:比如生产者唤醒生产者、消费者唤醒消费者,导致无效唤醒,效率低下。
而Lock接口搭配Condition类,能实现「定向精准通知」:生产者只唤醒消费者,消费者只唤醒生产者,零无效操作!
1. 核心逻辑
-
创建Lock锁对象(推荐ReentrantLock);
-
为不同角色创建专属Condition队列(生产者队列+消费者队列);
-
状态不满足时,调用对应Condition的await()进入等待;
-
状态满足后,调用目标角色的signal()/signalAll()精准唤醒。
2. 实战:进阶版多生产者多消费者
import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; // 复用Goods类(同上,核心逻辑不变) class AdvancedWarehouse { private static final int MAX_CAP = 4; // 仓库最大容量 private List<Goods> goodsList = new ArrayList<>(); private ReentrantLock lock = new ReentrantLock(); // Lock锁 private Condition producerCond = lock.newCondition(); // 生产者等待队列 private Condition consumerCond = lock.newCondition(); // 消费者等待队列 // 生产商品 public void produce(Goods goods) throws InterruptedException { lock.lock(); // 加锁 try { while (goodsList.size() == MAX_CAP) { System.out.println("仓库满," + Thread.currentThread().getName() + " 等待"); producerCond.await(); // 生产者进入专属队列等待 } goodsList.add(goods); System.out.println(Thread.currentThread().getName() + " 生产" + goods + ",库存:" + goodsList.size()); consumerCond.signalAll(); // 只唤醒消费者队列 } finally { lock.unlock(); // 必须在finally释放锁,避免死锁 } } // 消费商品 public Goods consume() throws InterruptedException { lock.lock(); try { while (goodsList.size() == 0) { System.out.println("仓库空," + Thread.currentThread().getName() + " 等待"); consumerCond.await(); // 消费者进入专属队列等待 } Goods goods = goodsList.remove(0); System.out.println(Thread.currentThread().getName() + " 消费" + goods + ",库存:" + goodsList.size()); producerCond.signalAll(); // 只唤醒生产者队列 return goods; } finally { lock.unlock(); } } } // 测试类 public class ConditionDemo { public static void main(String[] args) { AdvancedWarehouse warehouse = new AdvancedWarehouse(); // 3个生产者+2个消费者(多角色场景更能体现精准通知优势) for (int i = 1; i <= 3; i++) { int finalI = i; new Thread(() -> { for (int j = 1; j <= 4; j++) { try { Goods goods = new Goods("CP" + finalI + "-" + j, "日用品" + j); warehouse.produce(goods); Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } } }, "高级生产者" + i).start(); } for (int i = 1; i <= 2; i++) { new Thread(() -> { for (int j = 1; j <= 6; j++) { try { warehouse.consume(); Thread.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); } } }, "高级消费者" + i).start(); } } }
3. 核心优势
-
精准唤醒:避免无效竞争,效率比wait/notify高30%+;
-
灵活控制:可创建多个Condition队列,支持多角色协作(比如“厨师→服务员→顾客”三级协作);
-
锁粒度更细:Lock支持可中断锁、超时锁,比synchronized更灵活(后续文章详解)。
四、两种通信方案核心对比(面试速记)
|
对比维度 |
wait/notify |
Condition |
|
核心依赖 |
synchronized关键字 |
Lock接口(ReentrantLock) |
|
唤醒方式 |
随机唤醒/全部唤醒 |
定向精准唤醒(支持多队列) |
|
灵活性 |
低(仅1个等待队列) |
高(多个Condition队列) |
|
异常处理 |
必须处理InterruptedException |
必须处理InterruptedException |
|
适用场景 |
单生产者单消费者、简单协作 |
多生产者多消费者、复杂协作 |
|
面试出现频率 |
★★★☆☆ |
★★★★★(企业级开发首选) |
五、实战整合:订单配送协作系统(集合+IO+异常)
结合前文知识,开发一个贴近实际场景的「商家→配送员」协作系统,包含:
-
Lock+Condition精准通信;
-
ArrayList存储待配送订单;
-
IO流记录配送日志;
-
完整异常处理(保障系统稳定性)。
import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; // 订单实体 class Order { private String orderNo; private String goods; private double price; public Order(String orderNo, String goods, double price) { this.orderNo = orderNo; this.goods = goods; this.price = price; } @Override public String toString() { return "订单【" + orderNo + "】-" + goods + "(¥" + price + ")"; } public String getOrderNo() { return orderNo; } } // 订单配送中心(核心协作逻辑) class OrderDeliveryCenter { private static final int MAX_ORDER = 5; // 最大待配送订单数 private List<Order> orderQueue = new ArrayList<>(); private ReentrantLock lock = new ReentrantLock(); private Condition merchantCond = lock.newCondition(); // 商家等待队列 private Condition courierCond = lock.newCondition(); // 配送员等待队列 private PrintWriter logWriter; // 初始化日志流(IO知识点) public OrderDeliveryCenter() { try { logWriter = new PrintWriter(new FileWriter("delivery_log.txt", true)); } catch (IOException e) { System.out.println("日志初始化失败"); e.printStackTrace(); } } // 商家提交订单(生产) public void submitOrder(Order order) throws InterruptedException { lock.lock(); try { while (orderQueue.size() == MAX_ORDER) { System.out.println("待配送队列已满," + Thread.currentThread().getName() + " 等待"); merchantCond.await(); } orderQueue.add(order); System.out.println(Thread.currentThread().getName() + " 提交" + order + ",待配送数:" + orderQueue.size()); log("提交订单", order); courierCond.signal(); // 唤醒1个配送员 } finally { lock.unlock(); } } // 配送员取单配送(消费) public void deliverOrder() throws InterruptedException { lock.lock(); try { while (orderQueue.size() == 0) { System.out.println("无待配送订单," + Thread.currentThread().getName() + " 等待"); courierCond.await(); } Order order = orderQueue.remove(0); System.out.println(Thread.currentThread().getName() + " 取走" + order + "开始配送,剩余待配送:" + orderQueue.size()); log("开始配送", order); merchantCond.signal(); // 唤醒1个商家 } finally { lock.unlock(); } } // 日志记录 private void log(String type, Order order) { if (logWriter != null) { String log = System.currentTimeMillis() + "|" + Thread.currentThread().getName() + "|" + type + "|" + order.getOrderNo() + "\n"; logWriter.write(log); logWriter.flush(); } } // 关闭日志流(资源释放) public void closeLog() { if (logWriter != null) { logWriter.close(); } } } // 商家任务 class MerchantTask implements Runnable { private OrderDeliveryCenter center; private String merchantName; public MerchantTask(OrderDeliveryCenter center, String merchantName) { this.center = center; this.merchantName = merchantName; } @Override public void run() { for (int i = 1; i <= 6; i++) { try { Order order = new Order("ORDER-" + merchantName + "-" + i, "生鲜套餐" + i, 88.0 + i * 10); center.submitOrder(order); Thread.sleep(600); // 模拟接单耗时 } catch (InterruptedException e) { System.out.println(merchantName + "提交订单被中断"); e.printStackTrace(); } } } } // 配送员任务 class CourierTask implements Runnable { private OrderDeliveryCenter center; private String courierId; public CourierTask(OrderDeliveryCenter center, String courierId) { this.center = center; this.courierId = courierId; } @Override public void run() { for (int i = 1; i <= 9; i++) { try { center.deliverOrder(); Thread.sleep(800); // 模拟配送耗时 } catch (InterruptedException e) { System.out.println(courierId + "配送被中断"); e.printStackTrace(); } } } } // 测试类 public class OrderDeliverySystem { public static void main(String[] args) throws InterruptedException { OrderDeliveryCenter center = new OrderDeliveryCenter(); // 2个商家+3个配送员 new Thread(new MerchantTask(center, "水果超市"), "商家A").start(); new Thread(new MerchantTask(center, "生鲜店"), "商家B").start(); new Thread(new CourierTask(center, "C001"), "配送员1").start(); new Thread(new CourierTask(center, "C002"), "配送员2").start(); new Thread(new CourierTask(center, "C003"), "配送员3").start(); // 等待任务完成,关闭日志流 Thread.sleep(10000); center.closeLog(); System.out.println("===== 订单配送任务结束 ====="); } }
执行效果
-
商家提交订单时,若队列满则暂停,配送员取单后自动唤醒;
-
配送员无单时等待,商家提交后精准唤醒,无无效竞争;
-
所有操作记录到
delivery_log.txt,方便问题排查。
六、面试避坑&拓展练习
1. 面试高频问题(记牢直接加分)
-
Q:wait()和sleep()的区别?
A:① wait()释放锁,sleep()不释放;② wait()需配合synchronized,sleep()可单独使用;③ wait()唤醒后需重新竞争锁,sleep()时间到直接继续。
-
Q:Condition的signal()和notify()的区别?
A:signal()唤醒指定Condition队列的线程,notify()随机唤醒锁对象上的线程;Condition支持多队列,notify()仅支持单队列。
-
Q:多生产者多消费者场景,用wait/notify会有什么问题?
A:无效唤醒(生产者唤醒生产者)、永久等待(部分线程未被唤醒),推荐用Condition精准通知。
2. 拓展练习(强化实战)
尝试用Condition实现「厨师做菜→服务员传菜→顾客用餐」的三级协作,要求:
-
厨师做完菜通知服务员,服务员传完通知顾客;
-
用IO记录每一步操作日志;
-
避免虚假唤醒和无效唤醒。
👉 完整实现思路和源码,在GZH「咖啡Java研习室」回复【学习资料】即可获取!
七、明日预告&资源领取
今天我们掌握了线程通信的两种核心方案,解决了“多线程协作内耗”问题。明天将学习企业级开发必备技能——线程池,吃透核心参数、工作原理和实战应用,把多线程知识整合到最终项目中!
✅ 本文配套源码、多线程思维导图、面试题合集,已整理至GZH「咖啡Java研习室」,回复【学习资料】即可领取;
✅ 系列文章持续更新,关注公众号获取更多Java核心知识点、实战案例和面试干货,一起进阶Java工程师~
#Java多线程 #生产者消费者模型 #waitnotify #Condition #Java面试
更多推荐



所有评论(0)