Java Object notify() 方法(保姆级教程)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
- 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
在多线程编程中,线程之间的协作与通信是核心挑战之一。Java 提供了 Object
类的 wait()
、notify()
和 notifyAll()
方法,这些方法与 synchronized
关键字结合使用,能够实现线程间的高效协作。其中,notify()
方法作为通知机制的核心,常常让开发者感到困惑。本文将从基础概念出发,通过案例和类比,深入解析 Java Object notify() 方法的原理与用法,帮助开发者理解其在多线程环境中的实际应用场景。
一、线程同步基础:对象锁与 wait()
方法
1.1 对象锁(Monitor)的核心作用
Java 中的 synchronized
关键字通过对象锁(Monitor)实现线程同步。每个对象都拥有一个与生俱来的锁,当线程进入 synchronized
方法或代码块时,必须先获取该对象的锁。例如:
public synchronized void process() {
// 只有一个线程能执行这段代码
}
这个机制确保了同一时间只有一个线程可以修改共享数据,避免了竞争条件(Race Condition)。
1.2 wait()
方法:让线程进入等待状态
当线程需要等待某个条件满足时,可以通过 wait()
方法将自己放入对象的等待队列中,并释放当前锁。例如:
public synchronized void await() {
while (!condition) {
wait(); // 进入等待队列,释放锁
}
// 条件满足后继续执行
}
此时线程会暂停执行,直到被其他线程通过 notify()
或 notifyAll()
唤醒。
二、notify()
方法的运作原理与使用场景
2.1 notify()
的核心功能
notify()
方法用于唤醒在对象等待队列中等待时间最长的线程(非随机选择)。例如:
public synchronized void signal() {
// 修改共享数据后通知等待线程
notify(); // 唤醒一个等待线程
}
需要注意的是,被唤醒的线程并不会立即执行,而是需要重新竞争锁,才能继续执行后续代码。
2.2 形象比喻:厨师与顾客的协作
想象一个餐厅场景:
- 厨师(Producer):准备食物后,通过
notify()
通知“等待队列”中的第一个顾客(Consumer)。 - 顾客(Consumer):在食物未准备好时,通过
wait()
释放座位(锁),进入等待队列。
此时,notify()
就像服务员喊一声“您的餐点准备好了”,唤醒一个顾客来取餐,但顾客需要重新排队拿号(竞争锁)才能继续用餐。
三、notify()
与 notifyAll()
的区别与选择
3.1 notifyAll()
的唤醒逻辑
notifyAll()
会唤醒等待队列中的 所有线程,而 notify()
只唤醒一个。例如:
public synchronized void signalAll() {
notifyAll(); // 唤醒所有等待线程
}
关键区别:
| 方法 | 唤醒线程数量 | 适用场景 |
|---------------|--------------|----------------------------|
| notify()
| 单个线程 | 只需唤醒一个线程时(如单生产者单消费者) |
| notifyAll()
| 所有线程 | 需要唤醒多个线程时(如多个消费者等待同一资源) |
3.2 为什么 notifyAll()
更安全?
假设一个资源被多个线程竞争,若仅使用 notify()
,可能因线程调度顺序导致某些线程永远无法被唤醒。例如,两个消费者线程 A 和 B:
- 生产者调用
notify()
,可能只唤醒 A,而 B 仍处于等待状态。 - 若 A 的优先级较低,B 可能因无法竞争到锁而一直等待。
此时 notifyAll()
能确保所有线程都有机会竞争锁,避免死锁。
四、经典案例:生产者-消费者问题
4.1 问题描述
生产者线程生产数据,消费者线程消费数据。要求:
- 当缓冲区已满时,生产者等待;
- 当缓冲区为空时,消费者等待。
4.2 代码实现与 notify()
应用
public class ProducerConsumerExample {
private final Object lock = new Object();
private int buffer = 0;
private static final int CAPACITY = 10;
// 生产者线程
public void produce() {
synchronized (lock) {
while (buffer >= CAPACITY) {
try {
lock.wait(); // 缓冲区满时等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
buffer++;
System.out.println("Produced: " + buffer);
lock.notifyAll(); // 通知消费者
}
}
// 消费者线程
public void consume() {
synchronized (lock) {
while (buffer <= 0) {
try {
lock.wait(); // 缓冲区空时等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
buffer--;
System.out.println("Consumed: " + buffer);
lock.notify(); // 通知生产者
}
}
}
关键点解析:
- 生产者在
produce()
中,当缓冲区满时调用wait()
,释放锁并等待; - 消费后调用
notifyAll()
,确保所有等待的生产者/消费者有机会被唤醒; notify()
在消费后仅唤醒一个线程,但notifyAll()
更保险(因可能有多个生产者/消费者线程)。
五、常见误区与最佳实践
5.1 误区一:未在同步代码块中调用 wait()
/notify()
若在非 synchronized
区域调用这些方法,会抛出 IllegalMonitorStateException
。例如:
// 错误示例:未加锁直接调用
public void wrongUsage() {
lock.notify(); // 抛出异常!
}
正确做法:必须在 synchronized
方法或代码块中调用。
5.2 误区二:忽略循环检查条件
直接使用 if
条件判断可能导致“虚假唤醒”(Spurious Wakeup),应改用 while
循环:
// 错误示例:可能因线程调度导致条件不满足
if (buffer == 0) {
wait();
}
// 正确示例:循环检查条件
while (buffer == 0) {
wait();
}
5.3 最佳实践建议
- 始终在
synchronized
区域中调用wait()
、notify()
; - 优先使用
Condition
接口(Java 5+java.util.concurrent.locks
包),提供更灵活的条件控制; - 避免过度依赖
notify()
,考虑使用更高层的并发工具类(如BlockingQueue
)。
六、进阶扩展:与 Condition
接口的对比
6.1 Condition
的优势
Condition
接口(通过 newCondition()
方法创建)提供了更细粒度的控制,例如:
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
public void produce() {
lock.lock();
try {
while (buffer >= CAPACITY) {
notFull.await();
}
// 生产操作...
notEmpty.signal(); // 仅通知相关条件
} finally {
lock.unlock();
}
}
对比总结:
| 特性 | Object
方法 | Condition
接口 |
|--------------------|------------------------|--------------------------|
| 灵活性 | 固定绑定对象锁 | 支持多条件管理 |
| 异常处理 | 需手动处理中断 | 自动恢复中断状态 |
| 推荐场景 | 简单场景或兼容旧代码 | 复杂条件逻辑的高并发场景 |
结论:合理使用 notify()
方法提升多线程协作效率
Java Object notify()
方法是实现线程间协作的核心工具之一,但其正确使用需要开发者对线程状态转换、锁机制和条件判断有深刻理解。通过本文的案例分析与误区解析,开发者可以:
- 掌握
wait()
、notify()
的基本语法与协作流程; - 理解
notify()
与notifyAll()
的适用场景; - 避免常见错误,确保代码的健壮性与可维护性。
在实际开发中,建议优先使用更高层的并发工具类(如 BlockingQueue
或 CompletableFuture
),但在需要精细控制线程行为时,熟练运用 Object
的等待/通知机制仍是开发者不可或缺的技能。