wait/notify/notifyAll 方法的使用注意事项

wait/notify/notifyAll 方法的使用注意事项

wait/notify/notifyAll 方法的使用注意事项

  • wait/notify/notifyAll 方法的使用注意事项
    • 为什么 wait 必须在 synchronized 保护的同步代码中使用?
    • 为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?
    • wait/notify 和 sleep 方法的异同?

主要学习 wait/notify/notifyAll 方法的使用注意事项。

我们主要从三个问题入手:

  1. 为什么 wait 方法必须在 synchronized 保护的同步代码中使用?
  2. 为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?
  3. wait/notify 和 sleep 方法的异同?

为什么 wait 必须在 synchronized 保护的同步代码中使用?

首先,我们来看第一个问题,为什么 wait 方法必须在 synchronized 保护的同步代码中使用?

我们先来看看 wait 方法的源码注释是怎么写的。

public final void wait(long timeout)throws InterruptedException

导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。

当前的线程必须拥有该对象的显示器。

此方法使当前线程(称为T )将其放置在该对象的等待集中,然后放弃对该对象的任何和所有同步声明。 线程T变得禁用线程调度目的,并且休眠,直到发生四件事情之一:

  • 一些其他线程调用该对象的notify方法,并且线程T恰好被任意选择为被唤醒的线程。
  • 某些其他线程调用此对象的notifyAll方法。
  • 一些其他线程interrupts线程T。
  • 指定的实时数量已经过去,或多或少。 然而,如果timeout为零,则不考虑实时,线程等待直到通知。

然后从该对象的等待集中删除线程T ,并重新启用线程调度。 然后它以通常的方式与其他线程竞争在对象上进行同步的权限; 一旦获得了对象的控制,其对对象的所有同步声明就恢复到现状 - 也就是在调用wait方法之后的情况。 线程T然后从调用wait方法返回。 因此,从返回wait方法,对象和线程的同步状态T正是因为它是当wait被调用的方法。

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

  synchronized (obj) {while (<condition does not hold>)obj.wait(timeout);... // Perform action appropriate to condition} 

(有关此主题的更多信息,请参阅Doug Lea的“Java并行编程(第二版)”(Addison-Wesley,2000)中的第3.2.3节或Joshua Bloch的“有效Java编程语言指南”(Addison- Wesley,2001)。

如果当前线程interrupted任何线程之前或在等待时,那么InterruptedException被抛出。 如上所述,在该对象的锁定状态已恢复之前,不会抛出此异常。

请注意, wait方法,因为它将当前线程放入该对象的等待集,仅解锁此对象; 当前线程可以同步的任何其他对象在线程等待时保持锁定。

该方法只能由作为该对象的监视器的所有者的线程调用。 有关线程可以成为监视器所有者的方法的说明,请参阅notify方法。

  • 参数

    timeout - 以毫秒为单位等待的最长时间。

  • 异常

    IllegalArgumentException - 如果超时值为负。

    IllegalMonitorStateException - 如果当前线程不是对象监视器的所有者。

    InterruptedException - 如果任何线程在当前线程等待通知之前或当前线程中断当前线程。 当抛出此异常时,当前线程的中断状态将被清除。

  • 另请参见:

    notify()notifyAll()

在使用 wait 方法时,必须把 wait 方法写在 synchronized 保护的 while 代码块中,并始终判断执行条件是否满足,如果满足就往下继续执行,如果不满足就执行 wait 方法,而在执行 wait 方法之前,必须先持有对象的 monitor 锁,也就是通常所说的 synchronized 锁。那么设计成这样有什么好处呢?

我们逆向思考这个问题,如果不要求 wait 方法放在 synchronized 保护的同步代码中使用,而是可以随意调用,那么就有可能写出这样的代码。

在代码中可以看到有两个方法,give 方法负责往 buffer 中添加数据,添加完之后执行 notify 方法来唤醒之前等待的线程,而 take 方法负责检查整个 buffer 是否为空,如果为空就进入等待,如果不为空就取出一个数据,这是典型的生产者消费者的思想

但是这段代码并没有受 synchronized 保护,于是便有可能发生以下场景:

  1. 首先,消费者线程调用 take 方法并判断 buffer.isEmpty 方法是否返回 true,若为 true 代表buffer是空的,则线程希望进入等待,但是在线程调用 wait 方法之前,就被调度器暂停了,所以此时还没来得及执行 wait 方法。
  2. 此时生产者开始运行,执行了整个 give 方法,它往 buffer 中添加了数据,并执行了 notify 方法,但 notify 并没有任何效果,因为消费者线程的 wait 方法没来得及执行,所以没有线程在等待被唤醒。
  3. 此时,刚才被调度器暂停的消费者线程回来继续执行 wait 方法并进入了等待。

虽然刚才消费者判断了 buffer.isEmpty 条件,但真正执行 wait 方法时,之前的 buffer.isEmpty 的结果已经过期了,不再符合最新的场景了,因为这里的“判断-执行”不是一个原子操作,它在中间被打断了,是线程不安全的。

调度器暂停指的是操作系统中的线程调度器(也称为任务调度器)将当前正在执行的线程暂停,并将CPU资源分配给其他线程执行的过程。

这可能发生在多线程环境中,其中有多个线程竞争执行资源。

当一个线程被调度器暂停时,它的执行被中断,CPU资源被重新分配给其他线程。被暂停的线程会进入一种等待状态,直到调度器再次选择该线程执行。这可能由多种原因引起,例如:

  1. 时间片用尽: 在多任务操作系统中,每个线程通常被分配一个时间片(即CPU时间),用于执行它的任务。当线程的时间片用尽时,调度器会暂停该线程,并将CPU资源分配给其他线程。
  2. 等待某些资源: 如果线程在等待某些资源时,例如等待IO操作完成或等待锁释放,调度器可能会暂停该线程,并允许其他线程执行,直到资源可用。
  3. 优先级调度: 调度器可能会根据线程的优先级来调度线程的执行顺序。较高优先级的线程可能会在较低优先级的线程之前执行,从而导致较低优先级的线程被暂停。

假设这时没有更多的生产者进行生产,消费者便有可能陷入无穷无尽的等待,因为它错过了刚才 give 方法内的 notify 的唤醒。

我们看到正是因为 wait 方法所在的 take 方法没有被 synchronized 保护,所以它的 while 判断和 wait 方法无法构成原子操作,那么此时整个程序就很容易出错。

我们把代码改写成源码注释所要求的被 synchronized 保护的同步代码块的形式,代码如下。

dingchuan (3)

这样就可以确保 notify 方法永远不会在 buffer.isEmpty 和 wait 方法之间被调用,提升了程序的安全性。

另外,wait 方法会释放 monitor 锁,这也要求我们必须首先进入到 synchronized 内持有这把锁。

这里还存在一个“虚假唤醒”(spurious wakeup)的问题,线程可能在既没有被notify/notifyAll,也没有被中断或者超时的情况下被唤醒,这种唤醒是我们不希望看到的。虽然在实际生产中,虚假唤醒发生的概率很小,但是程序依然需要保证在发生虚假唤醒的时候的正确性,所以就需要采用while循环的结构。

这样即便被虚假唤醒了,也会再次检查while里面的条件,如果不满足条件,就会继续wait,也就消除了虚假唤醒的风险。

为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

我们来看第二个问题,为什么 wait/notify/notifyAll 方法被定义在 Object 类中?而 sleep 方法定义在 Thread 类中?主要有两点原因:

  1. 因为 Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。
  2. 因为如果把 wait/notify/notifyAll 方法定义在 Thread 类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时 wait 方法定义在 Thread 类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是哪把锁呢?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

wait/notify 和 sleep 方法的异同?

第三个问题是对比 wait/notify 和 sleep 方法的异同,主要对比 wait 和 sleep 方法,我们先说相同点:

  1. 它们都可以让线程阻塞。
  2. 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。

但是它们也有很多的不同点:

  1. wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
  2. 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。
  3. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
  4. wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

以上就是关于 wait/notify 与 sleep 的异同点。

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

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

相关文章

代码随想录算法训练营30期——总结篇

训练营的60天一眨眼也就过去了&#xff0c;简单谈谈自己的一些感受和收获吧。 自己是研一非科班出身&#xff0c;出于种种原因读研两三个月后就决定坚定转码了&#xff08;围城就是这样&#xff0c;很多时候由于信息差有些东西真没有之前想的那么美好&#xff09;。由于之前几…

论文浅尝 | GPT-RE:基于大语言模型针对关系抽取的上下文学习

笔记整理&#xff1a;张廉臣&#xff0c;东南大学硕士&#xff0c;研究方向为自然语言处理、信息抽取 链接&#xff1a;https://arxiv.org/pdf/2305.02105.pdf 1、动机 在很多自然语言处理任务中&#xff0c;上下文学习的性能已经媲美甚至超过了全资源微调的方法。但是&#xf…

pyflink1.18.0 keyby之后使用自定义keyedProcessfunction报错

现象概述 使用可自定义的keyedProcessFunction,本地运行没问题,数据输入输出没问题. 但是上线后出现问题,最终无疾而终,怀疑线上环境导致的. 环境 flink1.18.0(代码 线上 环境版本统一的) python3.9 报错关键词 - Failed to deserialize consumer record due to - Faile…

python之自动化(django)

1、安装 我用的是pip install Django 在命令行中安装 然后django-admin startproject autotext&#xff08;在命令行中&#xff09; 这句话是创建一个django 项目 然后切换到你所创建项目的目录下 输入&#xff1a; python manage.py runserver 当你出现以下错误时 You…

武汉灰京文化:直播游戏新时代的游戏宣传方式

随着互联网和科技的迅速发展&#xff0c;游戏产业也日益繁荣。传统的游戏宣传方式逐渐显现出一些不足&#xff0c;传统的广告渠道和媒体报道在一定程度上已经不能满足游戏行业的需求。然而&#xff0c;随着直播平台的兴起&#xff0c;直播游戏成为了一种新的游戏宣传方式&#…

程序员们正面临前所未有的压力

目前&#xff0c;开发者们正经历双重的心理压力。一方面&#xff0c;持续不断的裁员让整个行业在较短时间内失去了更多的岗位&#xff0c;这种情况自二十多年前的互联网泡沫以来前所未有。那些曾经门庭若市的资深开发者现在却连一个回应都难以获得。而随着 Devin 的推出&#x…

【原创】java+swing+mysql二手车交易管理系统

前言&#xff1a; 本文主要介绍了二手车交易管理设计与实现。首先&#xff0c;通过市场需求&#xff0c;我们确定了二手车的功能&#xff0c;通常的二手车交易系统都是B/S架构&#xff0c;然而我们今天要用javaswing去开发一个C/S架构的二手车交易管理系统&#xff0c;主要功能…

第八阶段:uni-app小程序 --首页开发(2)

一&#xff1a;分析页面布局 1.1: 功能 搜索框&#xff1a; 轮播图&#xff1a; 分类的导航区&#xff1a; 楼层区&#xff1a; 二&#xff1a; 利用命令创建home分支 git branch git checkout -b home git branch 三&#xff1a; 配置网络请求(main.js 入口函数&#x…

每天学习一个Linux命令之route

每天学习一个Linux命令之route 在Linux系统中&#xff0c;route命令是用于配置和管理网络路由表的工具。它允许管理员查看、添加、修改和删除路由表的条目&#xff0c;以确定网络流量的传输路径。以下是route命令的所有可用选项及其用法。 语法 route [选项] [目标] [命令]可…

【网络原理】TCP 协议中比较重要的一些特性(三)

目录 1、拥塞控制 2、延时应答 3、捎带应答 4、面向字节流 5、异常情况处理 5.1、其中一方出现了进程崩溃 5.2、其中一方出现关机&#xff08;正常流程的关机&#xff09; 5.3、其中一方出现断电&#xff08;直接拔电源&#xff0c;也是关机&#xff0c;更突然的关机&am…

【JVM】什么是运行时数据区?

什么是运行时数据区&#xff1f; 运行时数据区指的是JVM所管理的内存区域&#xff0c;其中分成两大类&#xff1a; 线程共享 – 方法区、堆 方法区&#xff1a;存放每一个加载的类的元信息、运行时常量池、字符串常量池。 堆&#xff1a;存放创建出来的对象。 线程不共享 – …

pycharm如何创建虚拟环境

在 PyCharm 中创建虚拟环境可以通过以下步骤完成&#xff1a; 1. 打开 PyCharm&#xff1a;首先&#xff0c;打开 PyCharm 集成开发环境。 2. 创建新项目或打开现有项目**&#xff1a;如果你要在一个新项目中创建虚拟环境&#xff0c;可以选择创建新项目。如果已经有现有项目…

ASP.NET-框架分类与详解

一、ASP.NET框架概述 ASP.NET是由微软公司推出的一种基于.NET框架的服务器端Web应用程序开发技术。它提供了丰富的工具和框架&#xff0c;用于开发各种规模的Web应用程序和服务。ASP.NET具有高度的灵活性和可扩展性&#xff0c;适用于不同规模和复杂度的项目。 在ASP.NET的生…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:TextTimer)

通过文本显示计时信息并控制其计时器状态的组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 TextTimer(options?: TextTimerOptions) 参数&#xff1a; 参数名参数类型…

IBM DataStage服务的启动和停止

IBM DataStage服务的启动和停止 1 DataStage服务启动1.1 启动DB21.2 启动ASB代理服务1.3 启动ASBNODE服务1.4 启动DS服务1.5 启动DS监控 2 DataStage服务停止2.1 停止DS监控2.2 停止DS服务2.3 停止ASBNODE服务2.4 停止ASB代理服务2.5 停止DB2 在IBM DataStage中&#xff0c;服务…

【Miniconda】一文了解conda虚拟环境的作用

【Miniconda】一文了解conda虚拟环境的作用 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的订阅和支持~ &am…

cuda out of memory后清理GPU缓存

省流&#xff1a; sudo fuser -v /dev/nvidia* |awk ‘{for(i1;i<NF;i)print "kill -9 " $i;}’ | sudo sh 事情是这样的&#xff0c;明明模型就不大&#xff0c;但是一直报错cuda out of memory. 输入nvidia-smi查看GPU利用率发现&#xff0c;整体GPU占用了9000…

跨域CORS

概述 同源策略 同源策略&#xff08;Sameoriginpolicy&#xff09;是一种约定&#xff0c;它是 浏览器 最核心、最基本 的安全功能。 因此 跨域问题 仅仅存在于 浏览器&#xff0c;走出浏览器 例如 curl、postman 就不存在跨域了。 所谓同源&#xff08;即指在同一个域&…

微信小程序关闭首页广告

由于之前微信小程序默认开启了首页广告位。导致很多老人误入广告页的内容&#xff0c;所以想着怎么屏蔽广告。好家伙&#xff0c;搜索一圈&#xff0c;要么是用户版本的屏蔽广告&#xff0c;或者是以下一个模棱两可的答案&#xff0c;要开发者设置一下什么参数的&#xff0c;如…

AI预测福彩3D第10弹【2024年3月16日预测--第2套算法重新开始计算第2次测试】

今天继续开始咱们第2套算法的验证&#xff0c;计划每套算法连续测试10期&#xff0c;达到50%的命中率即为较优的模型&#xff0c;可继续使用。老规矩&#xff0c;先上图表&#xff0c;再下结论~ 最终&#xff0c;经过研判分析&#xff0c;2024年3月16日福彩3D的七码预测结果如下…