一、线程间通信的核心场景
最典型的场景是生产者 - 消费者模型:
- 生产者线程:生产数据(往共享容器里放数据)
- 消费者线程:消费数据(从共享容器里取数据)
- 通信需求:容器满时生产者等待,容器空时消费者等待;生产 / 消费后唤醒对方。
二、Java 实现线程通信的 3 种核心方式
方式 1:使用 Object 类的 wait ()/notify ()/notifyAll ()(基础经典)
这是基于对象监视器(锁) 的通信方式,必须在synchronized代码块 / 方法中使用:
wait():让当前线程释放锁并进入等待状态,直到被唤醒。notify():唤醒等待该对象锁的一个线程(随机)。notifyAll():唤醒等待该对象锁的所有线程(推荐,避免线程永久等待)。
完整示例(生产者 - 消费者):
java
运行
// 共享资源:仓库(最多存1个产品)
class Warehouse {
private int product = 0; // 产品数量
// 生产产品(生产者调用)
public synchronized void produce() {
// 仓库已满,生产者等待
while (product >= 1) { // 用while而非if,防止虚假唤醒
try {
wait(); // 释放锁,进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产产品
product++;
System.out.println(Thread.currentThread().getName() + " 生产了产品,当前库存:" + product);
notifyAll(); // 唤醒消费者
}
// 消费产品(消费者调用)
public synchronized void consume() {
// 仓库为空,消费者等待
while (product <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费产品
product--;
System.out.println(Thread.currentThread().getName() + " 消费了产品,当前库存:" + product);
notifyAll(); // 唤醒生产者
}
}
// 生产者线程
class Producer implements Runnable {
private Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
// 循环生产5次
for (int i = 0; i < 5; i++) {
warehouse.produce();
try {
Thread.sleep(500); // 模拟生产耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者线程
class Consumer implements Runnable {
private Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
// 循环消费5次
for (int i = 0; i < 5; i++) {
warehouse.consume();
try {
Thread.sleep(800); // 模拟消费耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试类
public class WaitNotifyDemo {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
// 启动生产者和消费者线程
new Thread(new Producer(warehouse), "生产者1").start();
new Thread(new Consumer(warehouse), "消费者1").start();
}
}
关键解释:
wait()必须在synchronized块中调用,因为调用前需要先获取对象锁,调用后会释放锁 (这是和sleep()的核心区别:sleep()不会释放锁)。- 用
while判断条件而非if:防止 "虚假唤醒"(线程被唤醒后,条件可能已经不满足,需要重新检查)。 notifyAll()比notify()更安全:避免只唤醒同类线程(比如生产者唤醒生产者)导致死锁。
方式 2:使用 Condition 接口(JUC 包,更灵活)
Condition是 Java 并发包(java.util.concurrent)提供的增强版等待 / 唤醒机制,基于Lock锁实现,相比wait()/notify()更灵活(可以创建多个 Condition,实现精准唤醒)。
完整示例:
java
运行
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 共享资源:仓库
class Warehouse2 {
private int product = 0;
private final Lock lock = new ReentrantLock(); // 可重入锁
private final Condition producerCondition = lock.newCondition(); // 生产者条件
private final Condition consumerCondition = lock.newCondition(); // 消费者条件
// 生产产品
public void produce() {
lock.lock(); // 获取锁
try {
while (product >= 1) {
producerCondition.await(); // 生产者等待(替代wait())
}
product++;
System.out.println(Thread.currentThread().getName() + " 生产了产品,当前库存:" + product);
consumerCondition.signal(); // 精准唤醒消费者(替代notify())
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁(必须在finally中,防止异常导致锁不释放)
}
}
// 消费产品
public void consume() {
lock.lock();
try {
while (product <= 0) {
consumerCondition.await(); // 消费者等待
}
product--;
System.out.println(Thread.currentThread().getName() + " 消费了产品,当前库存:" + product);
producerCondition.signal(); // 精准唤醒生产者
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
// 测试类
public class ConditionDemo {
public static void main(String[] args) {
Warehouse2 warehouse = new Warehouse2();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
warehouse.produce();
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
}, "生产者1").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
warehouse.consume();
try { Thread.sleep(800); } catch (InterruptedException e) {}
}
}, "消费者1").start();
}
}
核心优势:
- 可以创建多个
Condition对象,实现 "精准唤醒"(比如只唤醒生产者 / 消费者),而notify()是随机唤醒。 Lock锁的获取和释放更灵活(可以在非代码块中使用),且支持尝试获取锁(tryLock())。
方式 3:使用 BlockingQueue(阻塞队列,最高效)
BlockingQueue是 JUC 包提供的阻塞队列,内置了线程通信逻辑,无需手动写 wait/notify,是生产环境中最推荐的方式。
核心特性:
- 队列满时,入队操作(
put())会阻塞生产者线程。 - 队列空时,出队操作(
take())会阻塞消费者线程。
完整示例:
java
运行
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
// 测试类(无需自定义仓库,直接用BlockingQueue)
public class BlockingQueueDemo {
// 创建容量为1的阻塞队列(模拟仓库)
private static final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);
public static void main(String[] args) {
// 生产者线程:往队列里放数据
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
queue.put(i); // 队列满时阻塞
System.out.println(Thread.currentThread().getName() + " 生产了产品" + i + ",队列大小:" + queue.size());
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者1").start();
// 消费者线程:从队列里取数据
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
Integer product = queue.take(); // 队列空时阻塞
System.out.println(Thread.currentThread().getName() + " 消费了产品" + product + ",队列大小:" + queue.size());
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者1").start();
}
}
输出效果:
plaintext
生产者1 生产了产品1,队列大小:1
消费者1 消费了产品1,队列大小:0
生产者1 生产了产品2,队列大小:1
消费者1 消费了产品2,队列大小:0
...(生产者和消费者交替执行)
核心优势:
- 无需手动处理锁和等待 / 唤醒,代码极简,不易出错。
- 支持多种队列类型:
ArrayBlockingQueue(数组实现)、LinkedBlockingQueue(链表实现)、SynchronousQueue(无容量队列)等。
三、核心方法对比
| 方式 | 核心 API | 优点 | 缺点 |
|---|---|---|---|
| wait()/notify() | Object 类方法 | 基础经典,无需额外依赖 | 只能随机唤醒,需手动处理锁,易出错 |
| Condition | Lock+Condition | 精准唤醒,锁更灵活 | 代码稍复杂,需手动释放锁 |
| BlockingQueue | put()/take() | 极简高效,生产环境首选 | 依赖 JUC 包,灵活性稍低(适合标准生产消费场景) |
总结
- 线程间通信的核心是共享资源 + 等待 / 唤醒,目的是让线程协同工作而非抢占资源。
- 入门学习用
wait()/notify()理解原理,进阶用Condition实现精准控制,生产环境优先用BlockingQueue(极简且不易出错)。 - 关键注意点:
wait()会释放锁,sleep()不会;必须在锁保护的代码块中使用等待 / 唤醒方法;用while判断条件防止虚假唤醒。