乌鲁木齐市网站建设_网站建设公司_前端工程师_seo优化
2025/12/30 1:55:25 网站建设 项目流程

文章目录

  • 为什么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!

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询