news 2026/2/28 8:05:26

为什么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/2/21 10:00:46

PyTorch-CUDA镜像文档编写规范

PyTorch-CUDA 镜像设计与工程实践:从环境隔离到高效开发 在深度学习项目中,最让人头疼的往往不是模型结构本身,而是“为什么代码在我机器上跑得好好的,换台设备就报错?”——这个问题背后,通常是 CUDA 版本…

作者头像 李华
网站建设 2026/2/26 6:20:45

Markdown生成幻灯片展示PyTorch项目汇报

PyTorch-CUDA容器化开发与自动化汇报实践 在深度学习项目日益复杂的今天,一个常见的困境是:研究人员花费大量时间在环境配置上,而非真正的模型创新。你是否经历过这样的场景?明明代码逻辑清晰、实验设计合理,却因为 t…

作者头像 李华
网站建设 2026/2/24 19:30:56

Docker Compose配置健康检查确保PyTorch服务可用性

Docker Compose 配置健康检查确保 PyTorch 服务可用性 在构建现代 AI 推理系统时,一个常见的尴尬场景是:容器明明已经“运行中”,但首次请求却返回 503 错误。排查后发现,模型还在加载——这正是典型的“容器已启动,服…

作者头像 李华
网站建设 2026/2/27 2:39:51

Markdown highlight高亮PyTorch关键代码段

PyTorch-CUDA-v2.8 镜像实战指南:从环境搭建到代码高亮表达 在深度学习项目中,最让人头疼的往往不是模型设计本身,而是“为什么我的代码在你机器上跑不了?”——环境不一致、CUDA 版本冲突、依赖缺失……这些问题消耗了大量本该用…

作者头像 李华
网站建设 2026/2/27 0:32:44

企业级武汉君耐营销策划有限公司员工信息管理系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】

摘要 在当今数字化时代,企业信息管理系统的需求日益增长,尤其是针对员工信息的高效管理与分析。武汉君耐营销策划有限公司作为一家快速发展的企业,传统的手工记录和分散的Excel表格管理方式已无法满足其业务需求,存在数据冗余、查…

作者头像 李华