<JavaEE> 多线程编程中的“等待和通知机制”:wait 和 notify 方法

目录

一、等待和通知机制的概念

二、wait() 方法

2.1 wait() 方法的使用

2.2 超时等待

2.3 异常唤醒

2.4 唤醒等待的方法

三、notify() 方法

四、notifyAll() 方法

五、wait 和 sleep 的对比


一、等待和通知机制的概念

1)什么是等待和通知机制?

线程是抢占式执行的,无法预知线程之间的执行顺序。

但有时程序员也希望能合理协调多个线程的执行顺序。

因此,在 Java 中使用了等待(wait)和通知(notify)机制,用于在应用层面上干预多个线程的执行顺序

应当注意的是,干预执行顺序并不是干预系统的线程调度策略(操作系统内核中的线程调度仍是无序的),而是使被指定的线程,主动放弃被系统调度的机会,直到其他线程对被指定的线程发出通知,这个线程才再次参与系统的线程调度。(排队都不排了,自然也就轮不到它了)

2)使用等待和通知机制主要涉及以下三个方法
wait()让线程进入等待状态。
notify()唤醒在当前对象上等待的一个线程。
notifyAll()唤醒在当前对象上等待的所有线程。
以上三个方法都是 Object 类的方法。

二、wait() 方法

2.1 wait() 方法的使用

1)wait() 方法需要配合 synchronized 关键字使用

wait() 方法必须在 synchronized 修饰的代码块或方法中使用,否则会抛出 IllegalMonitorStateException 异常。

2)使用锁对象调用 wait() 方法

虽然,wait() 是 Object 类的方法,任何对象都可以调用该方法。

但是为了实现等待通知机制,要求调用 wait() 的对象必须是锁对象,且这个锁对象要与 synchronized 指定的锁对象一致。

3)wait() 方法具体做了什么?

wait() 方法主要执行了以下三个操作:

<1>释放当前的锁。
<2>使当前线程进入等待队列。
<3>通过某些条件被唤醒时,重新尝试获取当前锁。

代码演示wait()使用方法和使用结果:

    public static void main(String[] args) throws InterruptedException {//创建锁对象;Object locker = new Object();System.out.println("wait前");//在 synchronized 代码块中调用 wait 方法;synchronized (locker){// wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;locker.wait();}System.out.println("wait后");}//运行结果:
wait前
...程序没有执行完毕,线程一直在 wait 。

2.2 超时等待

有时间限制的等待

上文中的代码,除非有其他线程唤醒,否则执行后会一直处于 wait 的状态,这就使得程序陷入了“停摆”状态。

在部分场景中,我们可以使用带时间参数的 wait() 方法来规避这个问题。即使没有其他线程唤醒 wait ,wait 仍会在超过规定时间后,自动唤醒,避免了程序的“停摆”。

代码演示带时间参数的wait()使用方法和使用结果:

    public static void main(String[] args) throws InterruptedException {//创建锁对象;Object locker = new Object();System.out.println("wait前");//在 synchronized 代码块中调用 wait 方法;synchronized (locker){// wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;locker.wait(3000);//3秒后线程被唤醒;}System.out.println("wait后");}//运行结果:
wait前
wait后成功执行完毕。

2.3 异常唤醒

代码演示通过抛出异常唤醒wait:

    public static void main(String[] args) {//创建锁对象;Object locker = new Object();Thread t1 = new Thread(()->{System.out.println("wait前");//在 synchronized 代码块中调用 wait 方法;synchronized (locker){// wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}//3秒后线程被唤醒;}System.out.println("wait后");});t1.start();//抛出异常,清除中断标志,唤醒t1线程。t1.interrupt();}//运行结果:
wait前
wait后
java.lang.InterruptedException抛出了InterruptedException后,线程被唤醒,继续执行。

2.4 唤醒等待的方法

唤醒等待的方法有三种:
<1>

超时等待:超过了 wait() 方法指定的等待时间。

<2>异常唤醒:通过其他线程调用该等待线程的 interrupted() 方法,抛出异常唤醒。
<3>notify() 方法:其他线程调用该对象的 notify() 方法。

三、notify() 方法

1)notify() 方法有什么作用?

notify() 方法可以唤醒等待的线程。

2)notify() 也要写在 synchronized 修饰的代码块或方法中

notify() 方法也是 Object 类的方法,所以任何对象都可以调用。

在操作系统的原生 API 中,也有 wait() 和 notify() 方法。与 wait() 方法不同,操作系统的原生 API 没有要求 notify() 必须在 synchronized 修饰的代码块或方法中使用。

但是,应注意,在 Java 中还是特别约定了 notify() 方法也是要放在 synchronized 修饰的代码块或方法中的。

3)wait() 和 notify() 方法是通过锁对象联系的

一个锁对象调用的 wait() 只能被同一个锁对象调用的 notify() 唤醒。

如果唤醒时,同一个锁对象有多个线程正在等待,此时只会随机唤醒一个。

4)执行 notify() 方法后,锁在什么时候释放?

在 notify() 方法后,当前线程不会马上释放锁对象,而是等到线程执行完 notify() 方法所在的代码块或方法后,才会释放锁对象。

这也是 Java 中约定 notify() 方法要放在 synchronized 修饰的代码块或方法中的原因。

代码演示通过 notify 唤醒wait:

    public static void main(String[] args) throws InterruptedException {//创建一个锁对象;Object locker = new Object();//创建一个线程;Thread t1 = new Thread(()->{System.out.println("wait前");//打印"wait前"后等待;synchronized (locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//被唤醒后打印"wait后";System.out.println("wait后");});t1.start();//休眠两秒,保证t1线程进入等待状态。Thread.sleep(2000);//打印"notify前"后唤醒;System.out.println("notify前");synchronized (locker){locker.notify();//在出代码块前,打印"notify后"。System.out.println("notify后");}}//运行结果:
wait前
notify前
notify后
wait后打印"wait前"之后进入阻塞等待,直到被notify唤醒之后才打印了"wait后"。

四、notifyAll() 方法

notifyAll() 方法有什么作用?

唤醒这个锁对象上所有等待的线程。

有多个线程使用同一个锁对象 wait ,当对这个锁对象使用 notifyALL() 方法时,所有在等待的线程都会唤醒。

但是需要注意,在唤醒之后,由于需要重新获取锁,此时被唤醒的线程必然要进行锁竞争,所以这些被唤醒的线程并不是同时就开始执行各自的代码了,而仍然是有先后顺序的执行,顺序依旧是随机的。


五、wait 和 sleep 的对比

相同点
都会使线程阻塞等待。
不同点wait() 方法sleep() 方法
用途用于线程间通信。用于线程阻塞等待。
用法

是 Object 类中的方法,

需要在被 synchronized 修饰的代码块或方法中使用。

是 Tread 类中的静态方法,

方法的使用与 synchronized 无关。

状态

被调用后,当前线程进入 BLOCK 状态并释放锁。

被调用后,当前线程进入 TIME_WAIT 状态。
唤醒

通常通过 notify 唤醒;

可以通过超时或抛出异常唤醒;

通常按设定的时间唤醒;

可以通过抛出异常唤醒;


阅读指针 -> 《单例模式的两种实现:“饿汉模式”和“懒汉模式”》

<JavaEE> 单例模式的两种实现:“饿汉模式”和“懒汉模式”-CSDN博客介绍了什么是单例模式和单例模式的两种实现模式。重点介绍了单例模式中,“懒汉模式”在多线程下的实现。https://blog.csdn.net/zzy734437202/article/details/134785459

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

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

相关文章

EM32DX-E4【C#】

1外观&#xff1a; ecat总线&#xff0c;分布式io 2电源&#xff1a; 靠近SW拨码&#xff1a;24V 中间&#xff1a;0V 靠近面板&#xff1a;PE接地 3DI&#xff1a; 6000H DI输入寄存器 16-bit &#xff08;16位输入&#xff09; 00H U8 子索引总数 01H Unsigned16 IN1…

事务管理 springboot

事务是一组操作的集合 它是一个不可分割的工作单位 这些操作 要么同时成功要么同时失败 Spring事务管理 #Spring事务管理日志 logging: level: org.springframework.jdbc.support.JdbcTransactionManager: debug

【深度学习】回归模型相关重要知识点总结

回归分析为许多机器学习算法提供了坚实的基础。在这篇文章中&#xff0c;我们将总结 10 个重要的回归问题和5个重要的回归问题的评价指标。 一、线性回归的假设是什么 线性回归有四个假设&#xff1a; 线性&#xff1a;自变量&#xff08;x&#xff09;和因变量&#xff08;y&…

CoreDNS实战(一)-构建高性能、插件化的DNS服务器

1 概述 在企业高可用DNS架构部署方案中我们使用的是传统老牌DNS软件Bind, 但是现在不少企业内部流行容器化部署&#xff0c;所以也可以将Bind替换为 CoreDNS &#xff0c;由于 CoreDNS 是 Kubernetes 的一个重要组件&#xff0c;稳定性不必担心&#xff0c;于此同时还可将K8S集…

智能优化算法应用:基于社会群体算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于社会群体算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于社会群体算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.社会群体算法4.实验参数设定5.算法结果6.参考…

【unity3D】unity中如何查找和获取游戏物体

&#x1f497; 未来的游戏开发程序媛&#xff0c;现在的努力学习菜鸡 &#x1f4a6;本专栏是我关于游戏开发的学习笔记 &#x1f236;本篇是unity中游戏物体的查找与获取 这里写自定义目录标题 获取当前物体的基本属性查找其它物体- 通过名称查找其它物体- 通过标签查找- 通过类…

使用UART和USART在STM32上进行双向通信

在本文中&#xff0c;我们将深入了解如何在STM32上使用UART&#xff08;通用异步收发传输器&#xff09;和USART&#xff08;通用同步异步收发传输器&#xff09;实现双向通信。UART和USART是常见的串口通信协议&#xff0c;通常用于与其他设备进行数据传输。我们将重点介绍如何…

基于PaddleSeg开发的人像抠图web api接口

前言 基于PaddleSeg开发的人像抠图web api接口&#xff0c;提取官方代码&#xff0c;适配各种系统&#xff0c;通过api的接口进行访问。 环境要求 1、Python3.7以上 2、源码&#xff08;文章最后下载&#xff09; 源码结构 测试module.py中添加如下代码&#xff1a; if __na…

字符串函数strlen的用法详解及其相关题目

strlne函数的使用 一.strlen函数的声明二.strlen函数的头文件三.相关题目代码1代码2题目1题目2题目3题目4题目5题目6 一.strlen函数的声明 size_t strlen ( const char * str );二.strlen函数的头文件 使用strlen函数我们需要使用以下头文件 #include <string.h>三.相…

Powercli常用命令

背景 vcenter web界面不如命令行快&#xff0c;且不能批量操作。 根据实际需求逐步补充使用到的powercli 命令。 00 通过bat脚本配置terminal标签页 在WindowsTerminal上配置新的标签页&#xff0c;实现打开标签页即默认连接vcenter。 脚本内容如下&#xff1a; echo off p…

Unity中C#使用协程控制Shader材质变化

文章目录 前言一、协程是什么二、在Unity中使用协程1、我们在 Start 中测试一下协程的执行顺序2、我们实现一个点击按钮实现角色受击效果 三、协程中的动画过渡1、首先&#xff0c;在协程内实现中毒并且消散的效果2、在 OnGUI 内&#xff0c;给一个新按钮使用刚刚定义的协程 四…

STM32通用定时器

本文实践&#xff1a;实现通过TIM14_CH1输出PWM&#xff0c;外部显示为呼吸灯。 通用定时器简介 拥有TIM2~TIM5、TIM9~TIM14 一共10个定时器&#xff0c;具有4路独立通道&#xff0c;可用于输入捕获、输出比 较&#xff0c;同时包含了基本定时去的所有功能。 通用定时器的结…

深入分析爬虫中time.sleep和Request的并发影响

背景介绍 在编写Python爬虫程序时&#xff0c;我们经常会遇到需要控制爬取速度以及处理并发请求的情况。本文将深入探讨Python爬虫中使用time.sleep()和请求对象时可能出现的并发影响&#xff0c;并提供解决方案。 time.sleep()介绍 首先&#xff0c;让我们来了解一下time.s…

前端——html拖拽原理

文章目录 ⭐前言⭐draggable属性&#x1f496; api&#x1f496; 单向拖动示例&#x1f496; 双向拖动示例 ⭐总结⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于 前端——html拖拽原理。 vue3系列相关文章&#xff1a; vue3 fastapi 实现选择目录所有文…

根据已有安装的cuda配置合适的pytorch环境

目前网络上根据电脑配置安装合适的深度学习环境的帖子已经很多了&#xff0c;但是现实中会出现很久之前已经安装了对应的cuda&#xff0c;但是现在忘记了当时安装的是什么版本。本文针对这一问题展开攻略。 1 cuda安装版本查询 我们在查询自己应该安装什么版本的cuda时&#…

【模电】直流通路与交流通路

直流通路与交流通路 通常&#xff0c;在放大电路中&#xff0c;直流电源的作用和交流信号的作用总是共存的&#xff0c;即静态电流、电压和动态电流、电压总是共存的。但是由于电容、电感等电抗元件的存在&#xff0c;直流量所流经的通路与交流信号所流经的通路不完全相同。因此…

【设计模式】职责链模式设计在线文档帮助系统

职责链模式设计在线文档帮助系统 任务三&#xff1a;使用职责链模式设计在线文档帮助系统 某公司欲开发一个软件系统的在线文档帮助系统&#xff0c;用户可以在任何一个查询环境中输入查询关键字&#xff0c;如果当前查询环境下没有相关内容&#xff0c;则系统会将查询按照一定…

获取Spring容器Bean工具类

获取Spring容器Bean工具类 1、创建SpringUtils工具类2、注册 SpringUtils工具类3、如果打包的是War方式&#xff0c;可能上面两个注册工具类的方法都没用 1、创建SpringUtils工具类 public class SpringUtils implements ApplicationContextAware {private static Application…

【鸿蒙应用开发】开发环境搭建及IDE安装使用

1.下载安装包 安装包下载地址&#xff1a; 点击跳转下载页面 可以根据自己的操作系统选择对应版本下载。 本文以Windows安装为例&#xff0c;Mac安装方式相同 2. 安装 下载好后&#xff0c;打开安装包&#xff0c;进入安装界面&#xff1a; 点击Next&#xff0c;进入安…

【Vue】使用 Vue CLI 脚手架创建 Vue 项目(使用命令行创建)

前言 在开始使用Vue进行开发之前&#xff0c;我们需要先创建一个Vue项目。Vue CLI&#xff08;Command Line Interface&#xff09;是一个官方提供的脚手架工具&#xff0c;可以帮助我们快速创建Vue项目。 步骤 打开终端或命令行工具&#xff0c;运行以下命令&#xff1a; vu…