本文配套源码、多线程思维导图已整理至GZH「咖啡Java研习室」,回复【学习资料】即可领取;系列文章持续更新,关注不迷路~

目录

  1. 为什么线程通信是面试高频?(痛点直击)

  2. 方案1:synchronized+wait/notify(基础版协作)

  3. 方案2:Lock+Condition(精准通知,面试重点)

  4. 两种方案核心对比(表格速记)

  5. 实战:订单配送协作系统(集合+IO+异常整合)

  6. 面试避坑&拓展练习

一、为什么线程通信是面试高频?(痛点直击)

多线程开发中,「线程同步」解决“抢资源不打架”,而「线程通信」解决“分工合作不内耗”——这俩是面试必问的核心组合!

举个反例:

  • 生产者疯狂生产,仓库满了还不停→内存浪费

  • 消费者无脑循环消费,仓库空了还在查→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. 核心逻辑

  1. 创建Lock锁对象(推荐ReentrantLock);

  2. 为不同角色创建专属Condition队列(生产者队列+消费者队列);

  3. 状态不满足时,调用对应Condition的await()进入等待;

  4. 状态满足后,调用目标角色的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实现「厨师做菜→服务员传菜→顾客用餐」的三级协作,要求:

  1. 厨师做完菜通知服务员,服务员传完通知顾客;

  2. 用IO记录每一步操作日志;

  3. 避免虚假唤醒和无效唤醒。

👉 完整实现思路和源码,在GZH「咖啡Java研习室」回复【学习资料】即可获取!

七、明日预告&资源领取

今天我们掌握了线程通信的两种核心方案,解决了“多线程协作内耗”问题。明天将学习企业级开发必备技能——线程池,吃透核心参数、工作原理和实战应用,把多线程知识整合到最终项目中!

✅ 本文配套源码、多线程思维导图、面试题合集,已整理至GZH「咖啡Java研习室」,回复【学习资料】即可领取;

✅ 系列文章持续更新,关注公众号获取更多Java核心知识点、实战案例和面试干货,一起进阶Java工程师~

#Java多线程 #生产者消费者模型 #waitnotify #Condition #Java面试

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐