Java并发编程实战 04 | 使用WaitNotify时要注意什么?

在 Java 中,wait()、notify() 和 notifyAll() 方法在多线程编程中主要用于线程间的协作和同步。理解这些方法的使用特点对于编写稳定的多线程程序至关重要。我们将从以下三个问题入手深入探讨它们的使用:

  1. 为什么必须在 synchronized 代码块中使用 wait() 方法?
  2. 为什么 wait 方法需要在循环中使用?
  3. wait/notify 和 sleep 方法之间的相似点和不同点?

为什么必须在 synchronized 代码块中使用 wait() 方法?

为了找到这个问题的答案,让我们反过来思考:如果我们不要求在synchronized代码块中使用wait方法,会发生什么问题?让我们看看这段代码。

public class QueueDemo {Queue<String> buffer = new LinkedList<String>();public void save(String data) {buffer.add(data);// // 因为可能有线程在等待 take(),所以通知它们notify();  }public String take() throws InterruptedException {while (buffer.isEmpty()) {wait();}return buffer.remove();}
}

代码中有两个方法:save 和 take。save 方法负责将数据添加到 buffer 中,并调用 notify 方法来唤醒之前等待的线程。

take 方法则检查 buffer 是否为空。如果为空,则进入等待状态;如果不为空,则从 buffer 中获取一个数据项。

这是一个典型的生产者-消费者模式,我将在后续的文章中详细探讨这一模式。

然而,这段代码没有受到 synchronized 关键字的保护,可能会出现以下情况:

  • 首先,消费者线程调用take方法,在take方法中通过buffer.isEmpty()判断buffer是否为空,如果为空,线程要进入等待状态,但是如果线程在调用wait方法之前就被调度器挂起了,此时方法wait还未执行。

  • 与此同时,生产者线程开始运行,并执行 save 方法。它向 buffer 中添加数据,并调用 notify 方法。然而,由于消费者线程的 wait 方法还未执行,因此notify 调用没有任何效果,因为没有任何线程在等待唤醒。

  • 接着,之前被调度器挂起的消费者线程恢复执行,并调用 wait 方法,进入等待状态。错过了先前的唤醒。

虽然消费者在调用 wait 方法之前已经判断了 buffer.isEmpty 的条件,但当 wait 方法实际执行时,之前的判断结果已经过期,因为 buffer 的状态可能已经发生了变化。

这里的“判断-执行”并不是一个原子操作,中途可能被打断,这导致了线程的不安全性。在这种情况下,消费者线程可能由于错过了生产者的 notify 调用而陷入无尽的等待状态。

你可以分别调用这两个方法来模拟一个生产者线程和一个消费者线程:

public class QueueDemo2 {Queue<String> buffer = new LinkedList<String>();public void save(String data) {System.out.println("Produce a data");buffer.add(data);notify();  // Since someone may be waiting in take()}public String take() throws InterruptedException {System.out.println("Try to consume a data");while (buffer.isEmpty()) {wait();}return buffer.remove();}public static void main(String[] args) throws InterruptedException {QueueDemo2 queueDemo = new QueueDemo2();Thread producerThread = new Thread(() -> {queueDemo.save("Hello World!");});Thread consumerThread = new Thread(() -> {try {System.out.println(queueDemo.take());} catch (InterruptedException e) {e.printStackTrace();}});consumerThread.start();producerThread.start();}
}//输出:
Try to consume a data
Produce a data
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateExceptionat java.lang.Object.notify(Native Method)at thread.basic.chapter4.QueueDemo2.save(QueueDemo2.java:13)at thread.basic.chapter4.QueueDemo2.lambda$main$0(QueueDemo2.java:28)at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at thread.basic.chapter4.QueueDemo2.take(QueueDemo2.java:19)at thread.basic.chapter4.QueueDemo2.lambda$main$1(QueueDemo2.java:33)

值得庆幸的是,你根本没有犯错的机会!因为如果 wait 方法和 notify 方法在没有被 synchronized 关键字保护的代码块中执行,Java 会直接抛出 java.lang.IllegalMonitorStateException 异常。

为了解决这个问题,我们需要对代码进行修改:

public class SyncQueueDemo2 {Queue<String> buffer = new LinkedList<>();public synchronized void save(String data) {System.out.println("Produce a data");buffer.add(data);notify();  // Since someone may be waiting in take()}public synchronized String take() throws InterruptedException {System.out.println("Try to consume a data");while (buffer.isEmpty()) {wait();}return buffer.remove();}public static void main(String[] args) throws InterruptedException {SyncQueueDemo2 queueDemo = new SyncQueueDemo2();Thread producerThread = new Thread(() -> {queueDemo.save("Hello World!");});Thread consumerThread = new Thread(() -> {try {System.out.println(queueDemo.take());} catch (InterruptedException e) {e.printStackTrace();}});consumerThread.start();producerThread.start();}
}//输出:
Produce a data
Try to consume a data
Hello World!

如您所见,程序成功运行,并将“Hello World!”正确打印到控制台。

为什么 wait 方法需要在循环中使用?

当线程调用 wait 方法后,有可能会发生“虚假唤醒”的情况,即线程可能在没有接收到 notify 或 notifyAll 的情况下被意外唤醒,而这是我们不希望看到的。

尽管在实际环境中发生虚假唤醒的概率很小,但程序仍然需要确保在这种情况下的正确性。因此,我们使用 while 循环结构来反复检查等待条件,从而保证线程在被唤醒时,只有在条件满足的情况下才会继续执行。

while (condition does not hold)obj.wait();

这样,即便是被误唤醒了,也会再次检查 while 中的条件,如果条件不满足,则继续 wait,这样就杜绝了误唤醒的风险。

wait/notify 和 sleep 方法之间的相似点和不同点?

以下是 wait 方法和 sleep 方法之间的相似之处:

  1. 阻塞线程:wait 和 sleep 都会导致当前线程进入阻塞状态。
  2. 响应中断:如果在等待过程中收到中断信号,两者都会响应并抛出 InterruptedException 异常。

但是,它们之间也存在着许多不同之处:

  1. 使用位置不同:wait 方法必须在 synchronized 修饰的代码块或方法中使用,而 sleep 方法没有这个要求,可以在任何地方使用。
  2. 锁处理方式不同:当 wait 方法执行时,线程会主动释放所持有的对象锁;而 sleep 方法不会释放锁,即使它是在同步代码块中执行。
  3. 恢复机制不同:sleep 方法需要指定一个时间,时间到后线程会自动恢复;而 wait 方法(不带参数的情况)表示线程将永久等待,直到被中断或被其他线程唤醒。
  4. 所属类不同:wait 和 notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/417987.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gazebo可能打不开的问题

如果经常遇到gazebo只能断网才能运行的时候&#xff0c;主要就是因为无法联网访问gazebo的在线模型库&#xff0c;此时我们一般无法在联网的情况下打开gazebo。 这个时候就直接将下载好的模型先放到~/.gazebo/models/文件夹下面即可&#xff1a; https://github.com/osrf/gazeb…

大语言模型(LLM)如何更好地继续预训练(Continue PreTraining)

预训练&#xff08;Pretraining&#xff09;是一个非常消耗资源的工作&#xff0c;尤其在 LLM 时代。随着LLama2的开源&#xff0c;越来越多人都开始尝试在这个强大的英文基座模型上进行中文增强。但&#xff0c;我们如何才能保证模型在既学到「中文知识」的情况下&#xff0c;…

简单的EasyCaptcha图片验证码学习

简单的EasyCaptcha图片验证码学习 1. 需求 图片验证码是一种常见的验证形式&#xff0c;它通过生成一串随机数字或符号&#xff0c;并加入一些干扰像素&#xff0c;最终生成用于验证的图片。这种验证码的设计旨在增加破解难度&#xff0c;主要通过加大干扰强度来提高安全性。…

vue3 element-plus form 表单 循环校验

需求&#xff1a; 表单是循环出来的&#xff0c;3个一组&#xff0c;每组对于前端来说是一样的&#xff0c; 需要校验输入框是否必填是否为小数或者整数 效果&#xff1a; 未输入--显示&#xff1a;请输入 输入不是小数或整数--显示&#xff1a;请输入整数或小数 输入正确…

Vue - 关于Router路由跳转时显示的animate.css动画

Vue - 关于Router路由跳转时显示的animate.css动画 在Vue中&#xff0c;操作路由跳转时页面是闪白的&#xff0c;没有动画效果&#xff0c;我们可以通过在router-view中设置transition&#xff0c;并搭配animate丰富的动画效果来美化路由跳转时的显示效果. 1.安装animate np…

Java使用类加载器解决类冲突,多版本jar共存

Java使用类加载器解决类冲突 1、案例说明2、打包新版本POI并将要调用的方法封装2.1、POM文件2.2、封装的方法 3、要使用多个POI版本的项目3.1、打包前面的项目生成一个jar包3.1、POM文件3.2、类加载器代码3.3、Jar加载工具3.4、最终调用 1、案例说明 项目中已经有了一个旧版本…

奖项再+1!通义灵码智能编码助手通过可信 AI 智能编码工具评估,获当前最高等级

阿里云的通义灵码智能编码助手参与中国信通院组织的可信AI智能编码工具首轮评估&#xff0c;最终获得 4 级评级&#xff0c;成为国内首批通过该项评估并获得当前最高评级的企业之一。 此次评估以《智能化软件工程技术和应用要求 第 2 部分&#xff1a;智能开发能力》为依据&…

别给智能猫砂盆花冤枉钱了!这三款好用智能猫砂盆哪个更好用?

节假日回老家&#xff0c;不方便带猫咪怎么办&#xff1f;而且猫咪这么能拉&#xff0c;猫砂盆里拉满了又怎么办&#xff1f;猫砂盆一满&#xff0c;就会变脏&#xff0c;变脏了小猫就会抗拒上厕所&#xff0c;从而在家里找其他干净的地方排泄&#xff0c;那我们要怎么保证不在…

python中 if __name__ == “__main__“的代码没被执行

运行pytest 和unittest时&#xff0c;if name “main”: 下的代码没有被执行&#xff0c;发现__name__等于模块名 一、“name” 的作用 1、 __name__是python的一个内置类属性&#xff0c;它天生就存在于一个 python 程序中。 2、直接运行python程序时&#xff0c;__name__的…

报错:Reached the max session limit(DM8 达梦数据库)

报错:Reached the max session limit - - DM8 达梦数据库 1 环境介绍2 数据库启动SYSTEM IS READY后面日志3 数据库刚启动日志4 达梦数据库学习使用列表 1 环境介绍 某项目无法连接数据库,报错:超过最大会话数限制 , 检查 dmdba ulimit -a openfiles 已改检查 dm.ini 其中 MAX…

提升效率!ArcGIS中创建脚本工具

在我们日常使用的ArcGIS中已经自带了很多功能强大的工具&#xff0c;但有时候遇到个人的特殊情况还是无法满足&#xff0c;这时就可以试着创建自定义脚本工具。 一、编写代码 此处的代码就是一个很简单的给图层更改别名的代码。 1. import arcpy 2. input_fc arcpy.GetParam…

针对不同区域的摄像头,完成不同的算法配置的智慧快消开源了

智慧快消视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。 基于多年的深度…

js逆向--cai招网

js逆向--cai招网 一、寻找加密入口1、抓包2、关键字搜索寻找加密入口二、调试js三、python代码实现一、寻找加密入口 1、抓包 目标数据来源网址为:https://search.bidcenter.com.cn/search?keywords=%E5%85%AC%E5%85%B1%E4%BD%8F%E5%AE%85,打开开发者工具进行抓包。 目标…

Web安全之GroovyShell讲解:错误与正确示范,安全问题与解决方案

1. 引言 Groovy 是一门基于 Java 虚拟机&#xff08;JVM&#xff09;的动态语言&#xff0c;而 GroovyShell 是 Groovy 提供的一个灵活强大的脚本执行工具。通过 GroovyShell&#xff0c;开发者可以在运行时动态执行 Groovy 脚本&#xff0c;它的灵活性非常适合那些需要动态编…

DPO:直接偏好优化简化语言模型的偏好训练

人工智能咨询培训老师叶梓 转载标明出处 如何精确控制大规模无监督语言模型&#xff08;LMs&#xff09;的行为一直是一个挑战。这些模型虽然通过大量数据学习到了广泛的世界知识和一些推理技能&#xff0c;但由于其训练的无监督性质&#xff0c;使得它们的行为难以精确控制。…

Error running tomcat: Can‘t find catalina.jar

一、错误描述&#xff1a; 在运行 java-web项目时出现报错&#xff1a;Error running tomcat: Can‘t find catalina.jar 二、错误原因&#xff1a; tomcat的路径错误&#xff0c;在idea中配置正确的tomcat路径 三、解决方法&#xff1a; 1.点击EditConfigurations 2.点…

RP2040 C SDK clocks时钟源配置使用

RP2040 C SDK clocks时钟源配置使用 &#x1f33f;RP2040时钟源API函数文档&#xff1a;https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_clocks &#x1f341;RP2040时钟树&#xff1a; 系统时钟源可以来自外部时钟输入&#xff08;exte…

4000字三合一!Stata、SPSS、MATLAB实现多元线性回归详解!

参加数学建模的小伙伴要注意了&#xff1a;多元线性回归几乎是所有分析方式里面最核心、最常用、最全面的模型&#xff0c;博主本科大致参加了10次数模&#xff0c;还有一次正大杯市场调研赛&#xff0c;其中获得拿得出手的奖有9次&#xff0c;有5次都用到了多元线性回归——至…

山东大学OLED透明展示柜案例:科技赋能,创新展示新体验

随着科技的飞速发展&#xff0c;显示技术也在不断突破传统界限&#xff0c;为各行各业带来了全新的展示体验。山东大学集成攻关大平台展厅近期引入了OLED透明展示柜&#xff0c;这一创新举措不仅提升了展厅的展示效果&#xff0c;还为参观者带来了前所未有的互动体验。 背景介绍…

KADAL | Kriging代理模型Python工具箱推荐

用于分析、设计优化和探索的Kriging工具箱 简介Required packagesQuick ExamplesContact特别感谢&#xff1a;参考资料 简介 用于分析、设计优化和探索的克里金法 (Kriging for Analysis, Design optimization, And expLoration, KADAL) 是由万隆理工学院 (Institut Teknologi…