Java中wait()和notify()的正确使用方式

 更新时间:2026年03月23日 09:54:09   作者:yingjuxia.com  
本文详细介绍了Java中wait()和notify()方法的作用,包括让线程阻塞并释放锁、唤醒等待线程等,通过实例演示了它们在多线程同步中的应用,,需要的朋友可以参考下

wait()notify()notifyAll()Object 类 的原生方法,是 Java 最早提供的线程间协作机制(属于低级别重量级的等待/通知机制)。

核心规则(必须全部记住,否则 100% 会出问题)

必须在 synchronized 块/方法中调用

  • wait()、notify()、notifyAll() 都要求当前线程持有同一个对象的监视器锁(monitor)
  • 否则抛出 IllegalMonitorStateException

wait() 会释放锁

  • 调用 wait() 后,当前线程会释放对象锁,进入该对象的等待队列(wait set)

notify() / notifyAll() 不释放锁

  • 只是唤醒等待队列中的一个/全部线程,但不立即把锁给被唤醒的线程
  • 只有当前持有锁的线程离开 synchronized 块后,被唤醒的线程才有机会竞争锁

最经典的写法模板(生产级必须这样写)

// 消费者
synchronized (lock) {
    while (conditionNotMet) {           // 必须用 while,不是 if!(防止虚假唤醒)
        lock.wait();                    // 释放锁并等待
    }
    // 条件满足,消费
    doConsume();
}

// 生产者
synchronized (lock) {
    // 生产
    doProduce();
    lock.notify();          // 或 notifyAll()
    // 离开 synchronized 块后,被唤醒的线程才有机会抢锁
}

为什么必须用 while 而不是 if?(虚假唤醒经典坑)

虚假唤醒(spurious wakeup):线程可能在没有被 notify 的情况下被系统唤醒(极少见,但 JVM 规范允许)。

// 错误写法(极易出问题)
if (queue.isEmpty()) {
    lock.wait();   // 被虚假唤醒后,可能直接往下执行,而队列还是空的
}

// 正确写法(生产环境唯一推荐)
while (queue.isEmpty()) {
    lock.wait();
}

完整经典示例:生产者-消费者(固定大小队列)

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {

    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 10;
    private final Object lock = new Object();

    class Producer implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {
                    synchronized (lock) {
                        while (queue.size() == MAX_SIZE) {
                            System.out.println("队列已满,生产者等待...");
                            lock.wait();
                        }
                        int item = (int) (Math.random() * 100);
                        queue.offer(item);
                        System.out.println("生产: " + item + ",当前大小: " + queue.size());
                        lock.notifyAll();  // 唤醒所有等待的消费者
                    }
                    Thread.sleep(500); // 模拟生产耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {
                    synchronized (lock) {
                        while (queue.isEmpty()) {
                            System.out.println("队列为空,消费者等待...");
                            lock.wait();
                        }
                        int item = queue.poll();
                        System.out.println("消费: " + item + ",当前大小: " + queue.size());
                        lock.notifyAll();  // 唤醒可能等待的生产者
                    }
                    Thread.sleep(800); // 模拟消费耗时
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();
        new Thread(pc.new Producer(), "生产者-1").start();
        new Thread(pc.new Consumer(), "消费者-1").start();
        new Thread(pc.new Consumer(), "消费者-2").start();
    }
}

常见错误写法汇总(你几乎一定会踩)

错误写法后果正确做法
在 synchronized 外面调用 wait()IllegalMonitorStateException必须在 synchronized 内
用 if 判断条件而不是 while虚假唤醒导致逻辑错误永远用 while
只用 notify() 而不用 notifyAll()可能导致部分线程永久等待(信号丢失)多消费者/生产者场景用 notifyAll
notify() 后立即修改共享变量可能导致被唤醒线程看到旧状态修改完再 notify
不同对象上 wait/notify线程永远唤不醒必须用同一个锁对象

2025–2026 年真实项目中的选择建议

场景推荐工具原因
简单生产者-消费者、线程间状态等待wait/notify轻量、无额外依赖
需要超时等待Condition.await(long, TimeUnit)ReentrantLock 的 Condition 更灵活
大多数现代业务代码BlockingQueue(ArrayBlockingQueue)封装好了 wait/notify,API 更安全
高并发、复杂条件等待Condition + ReentrantLock支持多个等待队列、公平锁、可中断
响应式/异步场景CompletableFuture / reactor基本不再用原始 wait/notify

一句话总结(面试/生产最常问的答案):

“wait() 和 notify() 必须在同一个对象的 synchronized 块中使用,wait() 会释放锁并进入等待队列,notify() 只唤醒但不释放锁,永远用 while 判断条件,多线程协作场景优先考虑 notifyAll()。”

以上就是Java中wait()和notify()的正确使用方式的详细内容,更多关于Java使用wait()和notify()的资料请关注脚本之家其它相关文章!

相关文章

  • eclipse/IDEA配置javafx项目步骤(图文教程)

    eclipse/IDEA配置javafx项目步骤(图文教程)

    这篇文章主要介绍了eclipse/IDEA配置javafx项目步骤(图文教程),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • SpringBoot无感刷新Token的实现示例

    SpringBoot无感刷新Token的实现示例

    无感刷新Token避免会话过期导致数据丢失,通过后端动态续签或前端主动请求,结合双Token机制,实现身份凭证自动更新,感兴趣的可以了解一下
    2025-07-07
  • 详解java中的static关键字

    详解java中的static关键字

    这篇文章主要介绍了java中的static关键字的的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • Java 将Excel转为SVG的方法

    Java 将Excel转为SVG的方法

    本文以Java示例展示如何将Excel文档转为SVG格式。通过本文中的方法,在将Excel转为SVG时,如果sheet工作表中手动设置了分页,则将每个分页的内容单独保存为一个svg文件,如果sheet工作表中没有设置分页,则将Excel sheet表格中默认的分页范围保存为svg。
    2021-05-05
  • Java实现简单QQ聊天工具

    Java实现简单QQ聊天工具

    这篇文章主要为大家详细介绍了Java实现简单QQ聊天工具,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • 一文掌握SpringBoot多环境配置

    一文掌握SpringBoot多环境配置

    在实际项目的开发过程中,我们程序往往需要在不同环境中运行,每个环境中的配置参数可能都会有所不同,例如数据库连接信息、文件服务器等等,下面小编给大家介绍SpringBoot多环境配置,感兴趣的朋友一起看看吧
    2024-04-04
  • Java替换字符串replace和replaceAll方法举例详解

    Java替换字符串replace和replaceAll方法举例详解

    这篇文章主要介绍了Java中替换字符串的几种方法,包括String类的replace()、replaceAll()、replaceFirst()方法,以及StringBuilder和StringBuffer类的replace()方法,还提到了一些第三方库,如Hutool,它们提供了更丰富的字符串处理功能,需要的朋友可以参考下
    2025-02-02
  • Spring使用IOC与DI实现完全注解开发

    Spring使用IOC与DI实现完全注解开发

    IOC也是Spring的核心之一了,之前学的时候是采用xml配置文件的方式去实现的,后来其中也多少穿插了几个注解,但是没有说完全采用注解实现。那么这篇文章就和大家分享一下,全部采用注解来实现IOC + DI
    2022-09-09
  • Java中数组的定义和使用教程(二)

    Java中数组的定义和使用教程(二)

    这篇文章主要给大家介绍了关于Java中数组的定义和使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • SpringBoot3整合 Elasticsearch 8.x 使用Repository构建增删改查示例应用

    SpringBoot3整合 Elasticsearch 8.x 使用Repository构

    我们构建了一个完整的 Spring Boot 3 和 Elasticsearch 8.x 的增删改查示例应用,使用 Spring Data Elasticsearch Repository,我们能够快速实现对 Elasticsearch 的基本 CRUD 操作,简化了开发流程,希望这个示例能够帮助你理解如何在项目中有效使用 Elasticsearch!
    2024-11-11

最新评论