Java中多线程wait和notify的用法

news2024/9/20 18:00:06

目录

一、wait和notify/notifyAll的由来

二、wait()方法

三、notify方法

3.1 notify的作用

3.2 wait和notify的 相互转换代码+图

3.3 notifyAll

四、为什么需要notify和wait都需要上锁?

五、wait和sleep的对比


前言:由于线程之间是抢占式执行的,因此线程之间的先后顺序难以判断,具有很强的”随机性“(ps:这里的随机并非数学上的随机,但确实是无法判断的)。

一、wait和notify/notifyAll的由来

Java为了保证实际开发中可以合理的协调各个线程执行的先后顺序,引入了三个方法。

  • wait() / wait(long timeout) : 让当前线程进入等待状态。
  • notify() / notifyAll() :唤醒在当前对象上等待的线程。

注意:wait ,notify,notifyAll 都是 Object 类的方法。

二、wait()方法

wait方法执行之后做了啥?其作用是什么?

  • 使当前执行代码的线程进行等待。(把线程放在等待队列中)
  • 释放当前锁(这需要调用wait的线程是加锁状态,这里的释放是指先暂停对Synchronized所修饰的代码块的运行,后续在重新获取锁的占有权后继续执行wait代码后面的部分)。
  • 满足一定条件时,让使用wait的线程被唤醒(其他线程中调用notify/notifyAll,或者代码抛出InterruptedException,线程宕掉等),需要重新获取这个锁(需要参与锁竞争)。

作用:当前线程因为某些原因需要阻塞等待,但是又不能影响后续线程的工作,于是需要调用wait方法,当阻塞完毕后(当前线程被唤醒),需要继续参与锁竞争后才能拿到当前锁。

下面我们来看以下代码:

 ps:synchronized 在JVM中 也叫做监控器锁。

这也正印证了我们上面所讲解的第二点:wait在运行的过程中会 释放当前锁

换句话来说: wait要搭配 synchronized 来使用,脱离synchronized使用wait会直接抛出异常。

于是我们将上述代码进行调整:

观察代码发现,object.wait() 之后就一直处于等待状态。这时候我们就需要notify方法来将其唤醒。

三、notify方法

3.1 notify的作用

唤醒等待中的线程。

  • notify是包含在Synchronized中的。
  • 线程1没有释放锁的情况下,线程2是无法调用notify(因为需要阻塞等待)
  • 线程1调用wait,在wait里面释放了锁(wait的功能就包含了释放锁,这个时候虽然线程1的代码运行的部分还在synchronized里面,但是锁处于释放状态),线程2才可以拿到锁。
  • 如果是多个线程等待,则就会发生锁竞争:即使线程1刚刚被唤醒了(之前拿到过锁),也还是要参加锁竞争。
  • 即使线程2调用notify后,线程1或者其他线程也不能马上拿到锁,而是需要等待notify方法的线程将程序执行完才行,即 线程2释放锁之后才行。

总结:要保证加锁的对象和调用wait的对象是同一个对象,调用wait的对象和调用notify的对象也是同一个对象。

3.2 wait和notify的 相互转换代码+图

例如:a.wait() 使用b.notify() 是无法将其唤醒的.(ps:这里的a,b都是对象名,非线程名)

这里将展示代码和画流程图来帮助理解:

public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        //第一个线程进行wait操作
        Thread t1 = new Thread(()-> {
            while(true) {
                System.out.println("wait 之前");
                synchronized (object) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("wait之后");
                }
            }
        });
        t1.start();
        Thread.sleep(5000);
        Thread t2 = new Thread(()-> {
            while(true) {
                System.out.println("notify 之前");
                synchronized (object) {
                    object.notify();
                    System.out.println("notify之后");
                }
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();
    }
}

 结果:

 配合流程图食用效果最佳:

 理解了notify之后,再来看notifyAll就很轻松了:

3.3 notifyAll

Java中除了有一个notify之外,还有一个notifyAll功能。多线程都执行了wait后,某个线程调用了notify,则是随机唤醒一个,如果是调用notifyAll就是将这些线程全部唤醒(当然了,他们都要参与锁竞争才能拿到锁,也就是说他们的执行顺序并不是并行而是串行的)。

四、为什么需要notify和wait都需要上锁?

我们发现无论是wait方法还是notify方法都必须处于synchronized所修饰的代码块内。这是为什么呢?

这其实是因为会造成丢失唤醒问题。

何为丢失唤醒问题?

就是在线程1执行wait之前,线程2执行了notify方法,提前将一个”醒“着的线程”唤醒“了。(这时的执行notify是没有产生任何作用的)。

举个例子:

分析

加了synchronized修饰后,线程1只要先拿到锁 (因为锁有独占性),这时候,线程2就不会出现在线程1还没有执行wait之前就调用notifv的可能性,这也正是为什么notify和synchronized要加锁的原因(因为两个加锁才能产生锁竞争,才能保证代码正确的运行)

五、wait和sleep的对比

其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,

唯一的相同点就是都可以让线程放弃执行一段时间。

总结:

  • wait需要搭配synchronized使用,sleep不需要。
  • wait是Object的方法,sleep是Thread的静态方法。

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

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

相关文章

Linux常用命令——tftp命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tftp 在本机和tftp服务器之间使用TFTP协议传输文件 补充说明 tftp命令用在本机和tftp服务器之间使用TFTP协议传输文件。 TFTP是用来下载远程文件的最简单网络协议,它其于UDP协议而实现。嵌入式linu…

RTMP协议封装H264和H265协议详解

RTMP协议封装H264和H265协议详解 文章目录RTMP协议封装H264和H265协议详解1 RTMP和FLV2 RTMP协议封装H264视频流2.1 RTMP发送AVC sequence header2.2 RTMP发送AVCC视频帧数据‘3 RTMP协议封装H265视频流1 RTMP和FLV 有关RTMP和FLV格式详细介绍可查看如下文章: http…

2022 Moonbeam的点点滴滴离不开社区支持

Moonbeam成为首个上线波卡的平行链已经有一周年🎂啦,这是一段疯狂的旅程🏍。 为了纪念这一时刻,我们通过公开数据来回顾这一年的众多里程碑、更新和整体发生的一切。 让我们来回顾一下Moonbeam在2022年取得了哪些成就吧。 &…

GIS二维电子地图开发总结

二维平面地图,目前支撑设备渲染,真实场景,后期电子围栏,运动轨迹等业务需求做铺垫 一、所涉及的技术栈: 1.Openlayers,加载渲染地图 2.Geoserver 发布wms和wfs()服务 3.Arcgis,Arcmap,进行源文件…

3.1、Ubuntu20桌面版远程连接SSHMobaXterm远程连接编辑器

连接SSH 安装系统完成并登陆后,输入 修改源码地址 进入apt文件夹 cd /etc/apt 备份文件 cp sources.list sources.list.bak 修改源码地址 vi sources.list # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to # newer versions of…

数据结构初级<排序>

本文已收录至《数据结构(C/C语言)》专栏! 作者:ARMCSKGT 你的阅读和理解将是我极大的动力! 目录 前言 排序的概念 常见排序简述 正文 直接插入排序 原理 代码实现 分析 希尔排序 原理 代码实现 分析 直接选择排序 原理 代码…

类加载的时机与过程

------ 摘自 周志明 《深入理解Java虚拟机》类加载的时机一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparati…

6、数组的常见运算

目录 一、数组的算术运算 二、数组的关系运算 三、数组的逻辑运算 一、数组的算术运算 (1)数组的加减运算:通过格式AB或A-B可实现数组的加减运算。但是运算规则要求数组A和B的维数相同。 示例1: A[1 2 3 4]B[2 4 6 8]C[1 1 …

三种简洁易行的方法解决基于Vue.js的组件通信

在总结Vue组件化编程的数据通信方面,看了网上的很多资料,都是讲父子组件的数据交互也就是参数传递,在组件的通信方面分几种情况,比如父子组件、非父子的兄弟组件、非父子的其他组件等等,这样看来,基于Vue.j…

STC15系列单片机EEPROM读写示例

STC15系列单片机EEPROM读写示例🌼STC15手册有关EEPROM描述 🌾STC15系列单片机内部集成了大容量的EEPROM,其与程序空间是分开的。利用ISP/IAP技术可将内部DataFlash当EEPROM,擦写次数在10万次以上。EEPROM可分为若干个扇区&#xf…

Android 蓝牙开发——蓝牙协议配置(七)

蓝牙主要分为两种模式,一种是媒体输出(Source)端,一种是媒体输入(Sink)端。也可以理解为服务端(Server)与客户端(Client)的关系。 蓝牙配置文件(B…

4-1指令系统-指令格式

文章目录一.指令的基本格式1.结构2.长度3.根据操作数地址码数目分类(1)零地址指令(2)一地址指令(3)二地址指令(4)三地址指令(5)四地址指令二.扩展操作码指令格…

Maven学习(二):Maven基础概念

Maven基础概念一、仓库二、坐标三、全局setting与用户setting区别一、仓库 仓库:用于存储资源,包含各种jar包;仓库分类: 本地仓库:自己电脑上的存储仓库,连接远程仓库获取资源;远程仓库&#x…

信息论复习—离散信道及其容量

目录 信道的简介: 信道的分类: 离散无记忆信道(DMC)模型: 转移概率: 离散无记忆信道的转移矩阵 输出仅与当前的输入有关: 后验概率: 离散无记忆信道的后验概率矩阵 &#xf…

spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法

spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法 目录spring-boot-starter-jdbc和mysql-connector-j依赖爆红的解决办法出现问题之前出现的问题:解决办法:方案一:第一种是继承 spring-boot-starter-parent 然后 依赖覆盖方案…

怎么用Python测网速?

“speedtest-cli” 是一个 Python 的第三方库,它可以用来在命令行中测试网络速度。它使用了 Speedtest.net 的服务器来进行测速,并可以提供下载和上传速度、延迟、丢包率等信息。使用这个库可以很方便地在终端中测试网络速度,而无需在浏览器中…

轻量级代码生成器加测试数据生成器

轻量级代码生成器加测试数据生成器介绍代码生成常用注解基本使用全局控制属性模板文件相关属性模板文件配置模拟数据生成自定义词库索引注意事项从已经存在的表完成映射,生成模板代码使用步骤Gitee项目链接 介绍 本项目是一个轻量级代码生成器,并提供多种方式来完成模拟数据的…

力扣(LeetCode)2299. 强密码检验器 II(C++/Python3)

题目描述 模拟 仅当密码包含强密码的所有特性,它是一个 强 密码。提示我们,遍历密码,维护 444 个标志,标志记录特性。遍历结束,根据标志判断特性。 class Solution { public:bool strongPasswordCheckerII(string pa…

MySQL建立数据库时字符集和排序规则的选择

文章目录前言一、字符集?二、Mysql中的字符集1.Unicode与UTF8、UTF8MB4、UTF16、UTF32关系2.Mysql新建数据库时选择哪种字符集呢?三、Mysql排序规则四、Mysql查询字符集和排序规则常用的命令前言 在MySQL中,字符集和排序规则是区分开来的&am…

【ArcGIS 小贴士】Pro VS ArcMap及软件获取

有朋友问我,ArcGIS Pro是不是比ArcMap好用。 我觉得用Pro的感觉,用Pro的感觉比ArcMap Ribbon界面 Pro采用的是Ribbon用户界面,与现在的微软的Office软件相似,功能的组织更加清晰。10.x版本的ArcGIS则是传统的工具条界面 有些朋友…