news 2026/4/3 2:31:39

为什么wait()、notify()和notifyAll()必须在同步机制中才能正常运行?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
为什么wait()、notify()和notifyAll()必须在同步机制中才能正常运行?

文章目录

  • 为什么wait()、notify()和notifyAll()必须在同步机制中才能正常运行?
    • 前言
    • 一、让我们先来复习一下基础知识
      • 1.1 什么是wait()?
      • 1.2 notify()的作用
      • 1.3 notifyAll()的作用
    • 二、为什么这三个方法必须在同步块中使用?
      • 2.1 不在同步块中使用会有什么后果?
      • 2.2 内存可见性问题
      • 2.3 解决方案:使用synchronized关键字
    • 三、总结
    • 因此,在多线程编程中,我们必须严格遵守这些规则,以避免潜在的程序 bug。
      • 📚 领取 | 1000+ 套高质量面试题大合集(无套路,闫工带你飞一把)!

为什么wait()、notify()和notifyAll()必须在同步机制中才能正常运行?

前言

大家好,我是闫工!今天我们要探讨一个看似简单却至关重要的问题:为什么wait()、notify()和notifyAll()这三个方法必须在同步机制中才能正常工作?

这个问题听起来好像很简单,但其实背后涉及到Java内存模型、线程同步以及锁机制的原理。作为一个有着丰富一线开发经验的老司机,今天我来带大家从一个全新的角度理解这个问题。

一、让我们先来复习一下基础知识

1.1 什么是wait()?

wait()是Object类中的一个方法,它会使得当前线程进入等待状态,并释放当前锁。简单来说,就是让当前执行的线程暂停执行,直到被其他线程唤醒。

代码示例:

publicclassTest{publicstaticvoidmain(String[]args){Objectlock=newObject();synchronized(lock){// 进入同步块,获取锁System.out.println("线程"+Thread.currentThread().getName()+"开始等待...");try{lock.wait();// 当前线程进入等待状态,并释放锁}catch(InterruptedExceptione){e.printStackTrace();}}}}

1.2 notify()的作用

notify()同样是Object类中的方法,它的作用是唤醒一个正在等待的线程。注意,这里说的是“一个”线程,而不是全部。

代码示例:

publicclassTest{publicstaticvoidmain(String[]args){Objectlock=newObject();synchronized(lock){// 获取锁System.out.println("线程"+Thread.currentThread().getName()+"正在运行...");try{lock.notify();// 唤醒一个等待的线程}catch(Exceptione){e.printStackTrace();}}}}

1.3 notifyAll()的作用

notifyAll()notify()类似,但它会唤醒所有正在等待的线程。这意味着如果有多个线程在等待某个锁,notifyAll()会让它们全部进入就绪状态。

代码示例:

publicclassTest{publicstaticvoidmain(String[]args){Objectlock=newObject();synchronized(lock){// 获取锁System.out.println("线程"+Thread.currentThread().getName()+"正在运行...");try{lock.notifyAll();// 唤醒所有等待的线程}catch(Exceptione){e.printStackTrace();}}}}

二、为什么这三个方法必须在同步块中使用?

2.1 不在同步块中使用会有什么后果?

现在我们来探讨关键问题:如果不在同步机制中使用这些方法,会发生什么?

假设我们有以下代码:

publicclassTest{publicstaticvoidmain(String[]args){Objectlock=newObject();// 注意这里没有加synchronized关键字try{lock.wait();// 不在同步块中调用wait()}catch(InterruptedExceptione){e.printStackTrace();}}}

运行这段代码,编译器不会报错,但运行时会抛出一个IllegalMonitorStateException异常。

为什么会这样?

因为wait()notify()notifyAll()都是与锁机制紧密相关的。它们必须在当前线程拥有该对象的锁时才能被调用。如果不在同步块中使用,程序就会试图操作一个没有上锁的对象,这显然是不安全的。

2.2 内存可见性问题

如果我们不使用同步机制,就无法保证内存可见性。例如:

假设有两个线程A和B:

  • 线程A修改了一个共享变量。
  • 线程B试图读取这个变量。

如果没有同步机制,线程B可能读取到的是一个过时的值,因为Java虚拟机(JVM)可能会缓存这个变量。这就是所谓的内存可见性问题。

示例代码:

publicclassTest{privatebooleanflag=false;publicstaticvoidmain(String[]args)throwsInterruptedException{Testtest=newTest();ThreadthreadA=newThread(()->{try{// 线程A的逻辑System.out.println("线程A开始运行...");test.flag=true;// 修改共享变量System.out.println("线程A修改了flag的值为true...");test.lock.wait();// 不在同步块中调用wait()}catch(InterruptedExceptione){e.printStackTrace();}});ThreadthreadB=newThread(()->{try{// 线程B的逻辑System.out.println("线程B开始运行...");while(!test.flag){// 试图读取共享变量System.out.println("线程B正在等待flag变为true...");Thread.sleep(100);}System.out.println("线程B检测到flag为true,继续执行...");}catch(InterruptedExceptione){e.printStackTrace();}});threadA.start();threadB.start();}}

运行这段代码可能会出现以下情况:

  • 线程B可能永远无法读取到flag的最新值(即true),导致无限循环。

这是因为没有同步机制,线程B无法看到线程A对共享变量所做的修改。这就是内存可见性问题的一个典型表现。

2.3 解决方案:使用synchronized关键字

为了解决上述问题,我们需要将这些方法放在同步块中,以确保内存可见性和互斥访问。

修改后的代码:

publicclassTest{privatebooleanflag=false;publicstaticvoidmain(String[]args)throwsInterruptedException{Testtest=newTest();ThreadthreadA=newThread(()->{synchronized(test){// 使用synchronized关键字try{System.out.println("线程A开始运行...");test.flag=true;// 修改共享变量System.out.println("线程A修改了flag的值为true...");test.lock.wait();// 在同步块中调用wait()}catch(InterruptedExceptione){e.printStackTrace();}}});ThreadthreadB=newThread(()->{synchronized(test){// 使用synchronized关键字try{System.out.println("线程B开始运行...");while(!test.flag){// 试图读取共享变量System.out.println("线程B正在等待flag变为true...");test.lock.wait();// 在同步块中调用wait()}System.out.println("线程B检测到flag为true,继续执行...");}catch(InterruptedExceptione){e.printStackTrace();}}});threadA.start();threadB.start();}}

现在,当线程A修改了flag的值后,它会调用wait()并释放锁。此时,线程B可以获取到这个锁,并读取到最新的flag值。

三、总结

通过上述分析,我们可以得出以下结论:

  1. wait()notify()notifyAll()必须在同步块中使用,否则会导致IllegalMonitorStateException异常。
  2. 不在同步块中使用这些方法可能会导致内存可见性问题,从而引发程序逻辑错误。
  3. 使用synchronized关键字可以确保线程之间的互斥访问和内存可见性。

因此,在多线程编程中,我们必须严格遵守这些规则,以避免潜在的程序 bug。

📚 领取 | 1000+ 套高质量面试题大合集(无套路,闫工带你飞一把)!

成体系的面试题,无论你是大佬还是小白,都需要一套JAVA体系的面试题,我已经上岸了!你也想上岸吗?

闫工精心准备了程序准备面试?想系统提升技术实力?闫工精心整理了1000+ 套涵盖前端、后端、算法、数据库、操作系统、网络、设计模式等方向的面试真题 + 详细解析,并附赠高频考点总结、简历模板、面经合集等实用资料!

✅ 覆盖大厂高频题型
✅ 按知识点分类,查漏补缺超方便
✅ 持续更新,助你拿下心仪 Offer!

📥免费领取👉 点击这里获取资料

已帮助数千位开发者成功上岸,下一个就是你!✨

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 0:25:21

GitHub Sponsors支持开发者:赞助PyTorch开源贡献者

GitHub Sponsors 支持 PyTorch 开发者:从开源贡献到高效开发的闭环 在人工智能技术飞速演进的今天,我们几乎每天都能看到新的模型、框架或训练技巧涌现。但在这股创新浪潮背后,真正支撑起整个生态运转的,往往不是某一篇惊艳的论文…

作者头像 李华
网站建设 2026/4/1 17:11:23

用Git管理深度学习实验代码的最佳Commit策略

用Git管理深度学习实验代码的最佳Commit策略 在深度学习项目中,你是否曾遇到过这样的场景: 训练了一个效果不错的模型,但几天后想复现结果时却发现——“这组超参数到底对应的是哪次代码版本?” 或者更糟:同事问你“上…

作者头像 李华
网站建设 2026/3/31 11:08:17

Docker top查看PyTorch容器进程状态

Docker top查看PyTorch容器进程状态 在深度学习项目的开发与部署过程中,一个常见的场景是:你启动了一个基于 PyTorch-CUDA 的 Docker 容器进行模型训练,一切看起来都正常,但几小时后发现训练似乎卡住了——GPU 利用率掉到了零&…

作者头像 李华
网站建设 2026/4/3 2:15:01

YOLOv11在PyTorch-CUDA-v2.8上的训练显存占用分析

YOLOv11在PyTorch-CUDA-v2.8上的训练显存占用分析现实挑战:为什么显存成了YOLOv11训练的“天花板”? 你有没有遇到过这样的场景?满怀期待地启动YOLOv11x的大模型训练,信心满满地设置batch size为32,结果几秒后终端弹出…

作者头像 李华
网站建设 2026/4/1 10:23:55

PyTorch模型推理性能优化:利用TensorRT与CUDA协同加速

PyTorch模型推理性能优化:利用TensorRT与CUDA协同加速 在如今的AI部署战场上,一个训练得再完美的模型,如果推理慢、延迟高、吞吐低,也难以真正落地。尤其是在边缘设备、实时视频分析或大规模在线服务中,用户可不会容忍…

作者头像 李华