news 2026/4/22 8:40:41

为什么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/22 8:39:42

Markdown嵌入交互式PyTorch可视化图表(Plotly)

Markdown嵌入交互式PyTorch可视化图表(Plotly) 在深度学习项目中,一个常见的痛点是:训练过程“黑箱化”——我们写代码、跑模型、看打印的日志,但很难直观地理解损失曲线的波动、准确率的跃迁,更别提把这些…

作者头像 李华
网站建设 2026/4/20 18:22:24

Jupyter Notebook单元格执行顺序陷阱提醒

Jupyter Notebook单元格执行顺序陷阱提醒 在深度学习项目的日常开发中,你是否遇到过这样的场景:明明修改了数据预处理逻辑,训练结果却毫无变化?或者两个看似完全相同的 notebook 跑出了截然不同的精度?这类“玄学”问题…

作者头像 李华
网站建设 2026/4/21 13:10:03

jupyter notebook插件推荐:提升PyTorch-CUDA-v2.8开发效率

Jupyter Notebook 插件推荐:提升 PyTorch-CUDA-v2.8 开发效率 在深度学习项目中,最让人头疼的往往不是模型结构设计或训练调参,而是环境配置——“为什么代码在我机器上跑得好好的,换台设备就报错?” 这种问题几乎每个…

作者头像 李华
网站建设 2026/4/21 0:06:46

System 3 觉醒:从“工具”到“物种”的根本改变

我们现在熟知的AI Agent,无论是AutoGPT还是各种Copilot,本质上都更像是一次性的“雇佣兵”。你给它一个任务,它甚至能规划出惊人的Chain-of-Thought(思维链),但一旦任务结束,会话重置&#xff0…

作者头像 李华
网站建设 2026/4/21 14:59:23

PyTorch-CUDA-v2.7镜像中安装NCCL以支持多节点通信

PyTorch-CUDA-v2.7镜像中安装NCCL以支持多节点通信 在当前大模型训练日益依赖分布式系统的背景下,单GPU已远远无法满足LLM或视觉Transformer等复杂网络的算力需求。越来越多团队从单机实验转向多节点集群训练,而这一跃迁的关键瓶颈往往不在计算本身&…

作者头像 李华
网站建设 2026/4/19 4:50:02

SpringBoot+Vue 武汉君耐营销策划有限公司员工信息管理系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】

摘要 随着信息技术的快速发展,企业对于高效管理员工信息的需求日益增长。传统的纸质档案或简单的电子表格管理方式已无法满足现代企业对数据安全性、实时性和便捷性的要求。武汉君耐营销策划有限公司作为一家专注于营销策划的企业,员工流动性较高&#x…

作者头像 李华