彻底理解Java线程通信wait / notify(原理 + 实战)

 更新时间:2026年03月26日 09:53:48   作者:消失的旧时光-1943  
在Java中,wait和notify是Object类的一部分,用于线程间的通信和同步,它们允许一个线程通知另一个线程某个事件的发生,或者请求释放对象的控制权,这篇文章主要介绍了Java线程通信wait/notify的相关资料,需要的朋友可以参考下

前言

在 Java 多线程开发中,wait()notify() 和 notifyAll() 是最经典但也最容易写错的一套线程通信机制。
它们用于解决:当线程因为“条件不满足”无法继续执行时,如何安全地等待并在条件改变后继续执行。

这篇文章将从原理、流程、示例到最佳实践,把这三兄弟一次讲透。

一、为什么需要 wait / notify?(最根本的问题)

在多线程场景中,经常会出现:

  • 消费者想消费,但仓库空了
  • 生产者想生产,但仓库满了
  • 主线程需要等待子线程准备好某个结果
  • 条件未满足前,线程无法继续往下执行

如果用 while(true) 死循环检查:

while (item == 0) {
    // 忙等(Busy waiting),疯狂消耗 CPU
}

这会导致:

  • CPU 空转
  • 性能极差
  • 线程争抢激烈

于是,Java 提供了 基于锁对象的条件等待机制

当条件不满足时,线程主动挂起自己,并释放锁;当条件满足时,被其他线程唤醒继续执行。

这就是 wait / notify

二、wait / notify 的正确理解(核心概念)

1. wait() 的本质动作

当线程执行:

lock.wait();

它会做 三件事

  1. 当前线程暂停执行(挂起)
  2. 释放 lock 的那把锁(让其他线程有机会修改状态)
  3. 把自己加入 lock 对象的“等待队列(Wait Set)”

线程会一直睡在那里,直到某个线程执行了:

  • lock.notify() 或 lock.notifyAll()

并且:

  • 🔥 唤醒的线程必须重新抢 lock 的锁
  • 抢到锁之后,才能继续执行 wait 后面的代码

2. notify() / notifyAll() 做了什么?

两个方法都必须在同步代码块中调用:

synchronized(lock) {
    lock.notify();
}

否则会抛:

IllegalMonitorStateException

notify() 作用:

  • 从 lock 的等待队列中 随机唤醒 1 个线程

  • 但唤醒 ≠ 立即执行

  • 被唤醒线程要在当前线程释放锁后才能抢锁执行

notifyAll() 作用:

  • 唤醒等待队列中的所有线程

  • 所有线程一起去抢锁,成功者继续执行

实际开发中一般推荐 notifyAll():更安全,避免唤醒错误线程导致死锁。

三、完整流程图解(非常关键)

假设线程 A 想消费商品,但仓库空了:

线程 A 做的事:

  1. synchronized(lock) → 拿到锁

  2. 判断仓库是否为空 → 是空的

  3. 调用 wait()

    • A 挂起

    • A 释放锁

    • A 进入等待队列

线程 B(生产者)做的事:

  1. synchronized(lock) → 拿到锁

  2. 生产商品

  3. 调用 notify() 或 notifyAll():唤醒 A

  4. B 退出 synchronized → 释放锁

最后:

  • A 被唤醒

  • 抢到锁后,继续执行 wait() 下一行的代码

四、生产者消费者完整 Demo(最经典示例)

class Depot {
    private int item = 0;
    private final Object lock = new Object();
    public void produce() throws InterruptedException {
        synchronized (lock) {
            while (item == 1) {   // 仓库满 → 等待
                lock.wait();
            }
            item = 1;
            System.out.println("生产了一个商品");
            lock.notifyAll();     // 通知消费者
        }
    }
    public void consume() throws InterruptedException {
        synchronized (lock) {
            while (item == 0) {   // 仓库空 → 等待
                lock.wait();
            }
            item = 0;
            System.out.println("消费了一个商品");
            lock.notifyAll();     // 通知生产者
        }
    }
}

为什么要用while而不是 if?

因为:

  • Java 的 wait 存在 虚假唤醒(Spurious Wakeups)

  • 被唤醒后必须重新判断条件

这是 JDK 官方文档明确要求的。

五、wait 和 sleep 有什么区别?(面试必考)

特性waitsleep
属于谁?ObjectThread
是否释放锁?释放不释放
是否必须在 synchronized 中?必须不需要
唤醒方式notify/notifyAll到点自动醒

一句话总结:

sleep 是让线程睡觉
wait 是让线程到等待队列里等别人叫醒

六、常见错误理解(90% 的人会踩坑)

1. 认为 notify 会立即让对方执行

错误!
notify 只是“叫醒”,对方必须等待 当前线程释放锁 后才能执行。

2. 不用 while,用 if 判断条件

会导致唤醒后条件不满足却继续执行 → 出 Bug

3. 以为 wait 是“做完事情后休息”

完全相反!

wait 是因为做不下去,不是因为做完了。

4. wait 和 notify 不在同一个锁对象上

比如:

a.wait();
b.notify();

永远唤不醒。

七、总结(建议背下来)

wait 的本质:

线程因为条件不满足 → 挂起自己 → 释放锁 → 进入等待队列 → 等别人唤醒。

notify / notifyAll 的本质:

条件改变 → 唤醒等待队列里的线程,让它们重新抢锁继续执行。

wait / notify 是同步代码里的“条件等待机制”,不是休眠机制。

八、这套机制与 RxJava 背压有什么关系?

其实本质一样:

  • RxJava 背压:下游忙 → 上游暂停、等待或限速

  • wait/notify:条件不满足 → 当前线程暂停等待其他线程改变条件

都是:

生产速度与消费速度不一致时的流量控制(Flow Control)思想。

总结 

到此这篇关于Java线程通信wait/notify的文章就介绍到这了,更多相关Java线程通信wait/notify内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java常用工具类之DES和Base64加密解密类

    java常用工具类之DES和Base64加密解密类

    这篇文章主要介绍了java常用工具类之DES和Base64加密解密类,需要的朋友可以参考下
    2014-07-07
  • Java的常见批量操作方法及注意事项

    Java的常见批量操作方法及注意事项

    Java作为一种强大的编程语言,提供了多种方法来有效地进行批量数据处理,这篇文章主要介绍了Java的常见批量操作方法及注意事项的相关资料,需要的朋友可以参考下
    2025-06-06
  • Mybatis-plus更新字段为null两种常用方法及优化

    Mybatis-plus更新字段为null两种常用方法及优化

    Mybatis Plus在进行更新操作时,默认情况下是不能将字段更新为null的,如果要更新字段为null,需要进行以下处理,这篇文章主要给大家介绍了关于Mybatis-plus更新字段为null的两种常用方法及优化,需要的朋友可以参考下
    2024-03-03
  • Java synchronized重量级锁实现过程浅析

    Java synchronized重量级锁实现过程浅析

    这篇文章主要介绍了Java synchronized重量级锁实现过程,synchronized是Java里的一个关键字,起到的一个效果是"监视器锁",它的功能就是保证操作的原子性,同时禁止指令重排序和保证内存的可见性
    2023-02-02
  • 如何解决通过spring-boot-maven-plugin package失败问题

    如何解决通过spring-boot-maven-plugin package失败问题

    这篇文章主要介绍了如何解决通过spring-boot-maven-plugin package失败问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Java日志框架之logback使用详解

    Java日志框架之logback使用详解

    这篇文章主要介绍了Java日志框架之logback使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Java双向链表按照顺序添加节点的方法实例

    Java双向链表按照顺序添加节点的方法实例

    这篇文章主要给大家介绍了关于Java双向链表按照顺序添加节点的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 解析Java和IDEA中的文件打包问题

    解析Java和IDEA中的文件打包问题

    这篇文章主要介绍了Java和IDEA中的文件打包问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • 使用springmvc运行流程分析,手写spring框架尝试

    使用springmvc运行流程分析,手写spring框架尝试

    这篇文章主要介绍了使用springmvc运行流程分析,手写spring框架尝试,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 详谈Java中net.sf.json包关于JSON与对象互转的坑

    详谈Java中net.sf.json包关于JSON与对象互转的坑

    下面小编就为大家分享一篇Java中net.sf.json包关于JSON与对象互转的坑,具有很好的参考价值,希望对大家有所帮助
    2017-12-12

最新评论