2026/4/15 23:51:57
网站建设
项目流程
国际网站空间,网站搭建阿里,外贸网站如何做外链,网站宣传软文文章目录为什么wait()和notify()必须在同步块中#xff1f;引言一、历史的回响#xff1a;Java设计者的初衷1. Java多线程机制的发展2. synchronized关键字的诞生3. 等待与通知的初衷二、核心原理#xff1a;为何必须依赖同步块1. 对象的“监视器”机制2. wait()和notify()的…文章目录为什么wait()和notify()必须在同步块中引言一、历史的回响Java设计者的初衷1. Java多线程机制的发展2. synchronized关键字的诞生3. 等待与通知的初衷二、核心原理为何必须依赖同步块1. 对象的“监视器”机制2. wait()和notify()的工作机制3. 不在同步块中使用wait()/notify()的风险三、实践误区常见的错误用法1. 错误的唤醒顺序2. 错误的锁对象3. 忽视InterruptedException四、正确使用wait()和notify()1. 确保同步块和锁对象一致2. 使用notifyAll()代替notify()3. 处理InterruptedException总结通过遵循这些最佳实践开发者可以更安全地在多线程环境中使用wait()和notify()方法。 领取 | 1000 套高质量面试题大合集无套路闫工带你飞一把为什么wait()和notify()必须在同步块中大家好我是闫工今天又要来和大家聊一聊Java多线程中的一个经典问题为什么wait()和notify()必须在同步块中这个问题看似简单但背后涉及到Java内存模型、锁机制以及线程安全的核心原理。作为一个资深的Java工程师我必须要用最通俗易懂的语言带大家深入理解这个问题的本质。引言多线程编程一直是Java世界的重头戏而多线程编程中又绕不开一个话题——等待与通知wait()和notify()。这两个方法就像是多线程世界中的“红绿灯”控制着线程的运行节奏。但是如果你不把它们放在同步块里使用那可就要小心了这不仅会让代码变得不可预测甚至会引发严重的程序错误。今天的文章中我会以一个轻松幽默的方式带大家理解这个问题从历史背景、核心原理到实际应用误区全面解析为什么wait()和notify()必须在同步块中。一、历史的回响Java设计者的初衷1. Java多线程机制的发展要理解为什么wait()和notify()必须在同步块中使用我们得先回顾一下Java多线程机制的发展历程。Java从诞生之初就内置了对多线程的支持这在当时是一个非常创新的设计。然而正如任何新技术一样早期的多线程编程也充满了挑战。2. synchronized关键字的诞生在Java中synchronized关键字是实现线程同步的基础工具。它的作用是保证同一时间只有一个线程可以执行某个代码块或方法。为了确保线程安全Java的设计者将wait()和notify()与synchronized紧密地绑定了在一起。3. 等待与通知的初衷wait()和notify()最初的设计目的是为了解决多线程之间的协作问题。比如一个线程可能需要等待某个资源准备就绪后才能继续执行而另一个线程则负责准备这个资源并通知等待的线程。为了确保这种协作不会引发竞态条件race condition或死锁deadlockJava的设计者规定了严格的使用规则——wait()和notify()必须在synchronized块中使用。二、核心原理为何必须依赖同步块1. 对象的“监视器”机制在Java中每个对象都有一个与之关联的“监视器”monitor。当一个线程进入一个synchronized块时它会自动获取该对象的监视器锁。这个锁的作用是确保同一时间只有一个线程可以执行被同步保护的代码。2. wait()和notify()的工作机制wait()当一个线程调用wait()方法时它会释放当前持有的监视器锁并进入等待状态。只有在接收到其他线程的通知notify()或notifyAll()后该线程才会重新竞争锁并恢复执行。notify()notify()的作用是唤醒一个处于等待状态的线程。如果多个线程在等待同一个对象监视器notify()只会随机唤醒其中一个。3. 不在同步块中使用wait()/notify()的风险假设我们不把wait()和notify()放在synchronized块中使用会发生什么呢让我们通过代码来模拟一下publicclassUnsafeWait{publicstaticvoidmain(String[]args){ObjectlocknewObject();// 线程A试图等待ThreadthreadAnewThread(()-{System.out.println(Thread A is waiting...);try{lock.wait();// 错误不在同步块中调用wait()}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread A is notified!);});// 线程B试图通知ThreadthreadBnewThread(()-{System.out.println(Thread B is notifying...);lock.notify();// 错误不在同步块中调用notify()System.out.println(Notification sent!);});threadA.start();threadB.start();}}运行这段代码时你可能会遇到以下问题IllegalMonitorStateException当你在没有获取监视器锁的情况下调用wait()或notify()Java会抛出这个异常。这是因为这些方法只能在持有对象监视器锁的前提下使用。竞态条件Race Condition即使没有出现上述异常线程之间的协作也会变得不可预测。比如某个线程可能在调用wait()之前就被另一个线程唤醒导致程序逻辑混乱。三、实践误区常见的错误用法1. 错误的唤醒顺序有些开发者可能会认为notify()可以随意使用但实际情况并非如此。例如publicclassNotifyOrderProblem{privateObjectlocknewObject();publicvoiddoWork()throwsInterruptedException{synchronized(lock){System.out.println(线程进入同步块...);// 假设这里需要等待某个条件满足while(!conditionIsMet()){// 检查条件lock.wait();// 等待通知}System.out.println(条件满足继续执行...);}}publicvoidtrigger(){synchronized(lock){setConditionMet(true);// 设置条件为已满足lock.notify();// 通知等待的线程}}}在这个例子中如果notify()和wait()没有正确地在同一个锁对象上使用就可能导致“假唤醒”spurious wakeups问题。虽然这种情况的概率较低但确实存在。2. 错误的锁对象另一个常见的错误是将wait()和notify()与不同的锁对象一起使用。例如publicclassWrongLockObject{privateObjectlock1newObject();privateObjectlock2newObject();publicvoidwaitMethod()throwsInterruptedException{synchronized(lock1){// 锁的是lock1System.out.println(线程A正在等待...);lock1.wait();// 正确但锁对象是lock1}}publicvoidnotifyMethod(){synchronized(lock2){// 锁的是lock2System.out.println(线程B正在通知...);lock2.notify();// 正确但与等待的线程无关}}}在这个例子中waitMethod()和notifyMethod()分别锁的是不同的对象。由于它们没有共享同一个监视器锁线程之间的协作就无法正常进行。3. 忽视InterruptedException在使用wait()方法时如果线程被中断interruptedJava会抛出InterruptedException异常。如果开发者不正确地处理这个异常可能会导致程序崩溃或逻辑错误。publicclassInterruptHandling{privateObjectlocknewObject();publicvoiddoWork()throwsInterruptedException{synchronized(lock){System.out.println(线程进入同步块...);try{lock.wait();// 等待通知可能被中断}catch(InterruptedExceptione){// 处理中断异常比如恢复中断状态或退出循环Thread.currentThread().interrupt();throwe;}}}}四、正确使用wait()和notify()1. 确保同步块和锁对象一致所有调用wait()和notify()的方法必须在同一个锁对象的synchronized块中。例如publicclassCorrectUsage{privateObjectlocknewObject();publicvoiddoWork()throwsInterruptedException{synchronized(lock){System.out.println(线程A进入同步块...);while(!conditionIsMet()){// 检查条件lock.wait();// 等待通知}System.out.println(条件满足继续执行...);}}publicvoidtrigger(){synchronized(lock){setConditionMet(true);// 设置条件为已满足lock.notifyAll();// 唤醒所有等待的线程}}}2. 使用notifyAll()代替notify()在某些情况下使用notify()可能会导致“死锁”deadlock或线程饥饿thread starvation。因此建议优先使用notifyAll()来唤醒所有等待的线程。synchronized(lock){setConditionMet(true);lock.notifyAll();// 唤醒所有等待的线程}3. 处理InterruptedException在调用wait()方法时必须正确处理InterruptedException异常。通常的做法是恢复中断状态或退出循环。synchronized(lock){try{while(!conditionIsMet()){// 使用while循环检查条件lock.wait();}}catch(InterruptedExceptione){Thread.currentThread().interrupt();// 恢复中断状态thrownewRuntimeException(线程被中断,e);}}总结wait()和notify()是Java中实现线程间协作的重要工具但它们的使用必须严格遵循特定规则。如果不正确地使用这些方法可能会导致各种并发问题如IllegalMonitorStateException、竞态条件和死锁等。为了确保程序的健壮性和正确性请记住以下几点始终在synchronized块中调用wait()和notify()。确保所有线程共享同一个锁对象。使用while循环而不是if来检查等待条件。优先使用notifyAll()以避免线程饥饿。正确处理InterruptedException异常。通过遵循这些最佳实践开发者可以更安全地在多线程环境中使用wait()和notify()方法。 领取 | 1000 套高质量面试题大合集无套路闫工带你飞一把成体系的面试题无论你是大佬还是小白都需要一套JAVA体系的面试题我已经上岸了你也想上岸吗闫工精心准备了程序准备面试想系统提升技术实力闫工精心整理了1000 套涵盖前端、后端、算法、数据库、操作系统、网络、设计模式等方向的面试真题 详细解析并附赠高频考点总结、简历模板、面经合集等实用资料✅ 覆盖大厂高频题型✅ 按知识点分类查漏补缺超方便✅ 持续更新助你拿下心仪 Offer免费领取 点击这里获取资料已帮助数千位开发者成功上岸下一个就是你✨