【Java EE初阶五】wait及notify关键字

1. wait和notify的概念

        所谓的wait和notify其实就是等待、通知机制;该机制的作用域join类似;由于多个线程之间是随机调度的,引入wait和notify就是为了能够从应用层面上,干预到多个不同线程代码的执行顺序,此处的干预,不是影响系统的线程调度策略(内核里调度线程任然是无序调度);

        简单来说就是在应用程序代码中,让后执行的线程,主动放弃被调度的机会,就可以让先执行的线程,先把对应的代码执行完成;

2. wait和notify的作用

2.1 例子引入

        现在有很多人要去ATM里,其中A是取钱,B是存钱的,C是运钞票的工作人员,负责给ATM机补充钱,防止ATM机没钱了,别人取不到钱。

        而这里的A,B,C被当做是线程,每次去ATM机里,只能有一个人进去,相当于上锁了,其他人不能进去,且处于被阻塞等待的状态,等ATM机里面的人完成操作出来后,别的人才能进去,但是这里是多线程随机调度的原因,其他线程会有锁竞争。     

        其中当A进去ATM机里后,就上锁,其他人不能进去;

        从线程调度执行的角度来看,如果把A比作是线程,当A线程进去后就会上锁,A线程要进行取钱的操作,其他线程不能进去操作,当A线程执行完自己的操作后,其他线程才能去锁竞争。

        但是如果ATM机里面没有钱时,A线程就不能完成取钱这个操作,它会退出ATM机,但退出后呢,它就因为没有取到钱而想继续进去ATM里面取钱,从而完成完成取钱这个操作;

        但是A依旧就会继续和其他线程进行锁竞争,又因为A线程之前拿到了锁,处于RUNNABLE状态(本来就轮到A取),其他线程因为阻塞,处于BLOCKED状态,需要被系统唤醒后,才能去竞争锁;

        总体来说,在ATM中依旧没有钱,线程A不用唤醒就能去竞争锁,所以A线程拿到锁的可能性还是很大的。但是如果这样子,那线程A频繁的在ATM门口反复横跳,始终占据的锁很长时间,导致其他线程(包括给ATM机送钱的线程)也不能进去操作,这样也就出现线程安全问题了。对于其他线程,始终无法拿到锁,这个情况称为 “线程饿死”。

        上述所说的线程A的代码大概逻辑是这样的:

        当A线程没有取到钱,就会一直重复加锁,解锁的操作。虽然这样的bug没有死锁那么严重,但也是要解决的。这时,就可以用wait和notify机制期望改进成如下图逻辑所示的效果:

这里的wait内部做了三件事

(1)释放锁,给其他线程竞争锁

(2)进入阻塞等待,让及时需要操作的线程运行

(3)等其他线程使用notify后,解除wait,参与到锁竞争中

2.2 wait和notify的使用

        wait的使用前提必须是当前对象被上锁了才能使用,不能你对象没被上锁,就wait了,那也不知道是在wait谁。

        有线程wait后,也必须有其他线程notify来释放这个wait,不然这个wait就会一直阻塞。

2.2.1  没有上锁的wait

        代码如下:

package thread;public class ThreadDemo29 {public static void main(String[] args) throws InterruptedException {Object locker = new Object();locker.wait();}
}

        结果如下:

2.2.2 当一个线程被wait,但没有其他线程notify来释放这个wait

        代码如下:

package thread;public class ThreadDemo29 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("wait之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait之后");}});t1.start();}
}

        结果如下:

        当前锁对象在进行wait之后,没有在主线中使用notify来唤醒,导致该线程t1一在处于阻塞状态;

2.2.3 两个线程,有一个线程wait,有一个线程notify来释放wait

        代码如下:

package thread;public class ThreadDemo29 {public static void main(String[] args) {Object locker = new Object();Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1 wait之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2 wait之后");}});Thread t2 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker) {System.out.println("t2 notify之前");locker.notify();System.out.println("t2 notify之后");}});t1.start();t2.start();}
}

        结果如下:

        我们代码里释放wait的notify,用的锁对象必须是要一样的,如果不一样,wait是不能被释放的,t1也就不能被唤醒了.

        在系统中,notify可以不用上锁,但是在java中,规定了要上锁,而且上锁的对象也要和notify对象一样,所以和系统是有区别的。

        结果具体分析:

结果解析:

        t1 和 t2 执行的时候:

        1、因为t1 sleep了1秒,所以能保证t1 先wait,所以先打印 “t1 wait之前”,这时,t1就进入阻塞等待状态。

        2、t2线程sleep了1秒后,获得这个locker锁,打印“t2 notify 之前”,当t2线程执行了notify后,t1 线程的wait就被释放了。

        3、因为t2还在持有锁,所以t1会还会进入阻塞,t2打印 “t2 notify之后” ,释放锁。

        4、t1拿到锁,再打印“t1 wait之后”。

2.2.4 notifyAll

        唤醒等待这个对象的所有线程;

        假设有很多个线程,都使用同一个对象wait,这时,使用notifyAll,所有使用了这个对象的wait的线程,都会被唤醒。

        但是当这些线程都被唤醒时,就要重新获取锁,他们还是要进行锁竞争的,这里也就相当于串行执行了(线程调度还是随机调度的)。而且使用notifyAll后,全部使用同一对象wait的线程,都被唤醒了,不好控制,更加推荐使用notify。

2.3 wait的三个选项

       没有参数的就是死等,但是很多情况,死等是不合理的,所以我们加参数,就是让某个线程在一定时间wait,如果超出了这个时间,就不wait了,直接去掉wait。

        有一个参数的精确范围是毫秒级别,两个参数的精确范围是纳秒级别。

3. wait、sleep、join的区别

        wait:需要搭配synchronized使用,线程wait时,处于WAITING状态,需要其他线程notify后,才能被唤醒,或者设置时间,到时就唤醒,可以兜底。

        sleep:线程sleep时,要到一定休眠时间才能被唤醒,但是也能被interrupt终止,但是这种情况是会抛异常的,是非常规手段,不符合我们预期的效果。

        join:啥线程调用join,当前线程就要等啥线程执行完,才能之前当前线程;和wait一样有参数可以选择,到时就不等了。

ps:本次的内容就到这里了,如果感兴趣的话就请一键三连哦!!!


 

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

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

相关文章

进程等待(wait和wait函数)【Linux】

进程等待 wait和wait函数【Linux】 进程等待的概念进程等待的必要性进程等待的方法wait函数waitpid函数 非阻塞等待和阻塞等待的对比阻塞等待:非阻塞等待 进程等待的概念 进程等待就是通过 wait/waitpid的方式,让父进程对子进程进行等待子进程退出并且将…

Planarized sentence representation for nested named entity recognition

原文链接: https://www.sciencedirect.com/science/article/pii/S0306457323000894?via%3Dihub Information Processing & Management 2023 介绍 问题 可以通过枚举span的方法来解决嵌套实体,然而目前的模型忽略了span之间的语义依赖关系&#xff…

2023我的编程之旅-地质人的山和水

引言 大家好,我是搞地质的。外行人有的说我们游山玩水,有的说我们灰头土脸,也有的说我们不是科学。 而我说,这是一门穷极一生青春,值得奉献的行业。这是一门贴近民生,又拥抱自然的学科。他的真理性在于探…

长见识!!!Java中除了消息队列之外,竟然还能这样实现异步任务

今年IT寒冬,大厂都裁员或者准备裁员,作为开猿节流主要目标之一,我们更应该时刻保持竞争力。为了抱团取暖,林老师开通了《知识星球》,并邀请我阿里、快手、腾讯等的朋友加入,分享八股文、项目经验、管理经验…

java进阶四-深入理解泛型和注解

泛型和注解是框架技术必备的技能 5 泛型5.1泛型理解5.1.1 泛型概念5.1.2 泛型的特点5.1.3 如何理解Java中的泛型是伪泛型?5.1.4 泛型的价值 5.2 泛型语法5.2.1 泛型类5.2.2 泛型接口3.2.3 泛型方法3.2.4泛型的上下边界3.2.5创建泛型数组 5.3泛型应用场景5.3.1数据库…

SQL Server注入之攻防技战法

那天下着很大的雨,母亲从城里走回来的时候,浑身就是一个泥人,那一刻我就知道我没有别的选择了 1.Mssql报错注入 0.判断数据库类型 1.爆当前用户名 2.爆版本 3.爆服务器名 4.判断数据库个数 5.获取全部数据库 语句只适合>2005 爆当前数据…

Windows下Jenkins自动化部署SpringBoot应用

Windows下Jenkins自动化部署SpringBoot应用 1、下载安装包 下载地址: 一个是 msi 程序: https://mirrors.aliyun.com/jenkins/windows/ 一个是 war 程序: https://get.jenkins.io/war-stable/ https://mirrors.jenkins.io/war/ 这里我…

机器人动力学一些笔记

动力学方程中,Q和q的关系(Q是sita) Q其实是一个向量,q(Q1,Q2,Q3,Q4,Q5,Q6)(假如6个关节) https://zhuanlan.zhihu.com/p/25789930 举个浅显易懂的例子,你在房…

GO语言笔记1-安装与hello world

SDK开发工具包下载 Go语言官网地址:golang.org,无法访问Golang中文社区:首页 - Go语言中文网 - Golang中文社区下载地址:Go下载 - Go语言中文网 - Golang中文社区 尽量去下载稳定版本,根据使用系统下载压缩包格式的安装…

西北工业大学计算机组成原理实验报告——verilog前两次

说明 为了有较好的可读性,报告仅仅粘贴关键代码。该PDF带有大纲功能,点击大纲中的对应标题,可以快速跳转。 实验目标 掌握单周期CPU执行指令的流程和原理;学习使用verilog HDL语言实现单周期CPU, 并通过功能仿真;提…

【教学类-43-14】 20240103 (4宫格数独:正确版:576套) 不重复的基础模板数量:576套

作品展示::——4宫格 576套不重复模板(48页*12套题) 背景需求: 生成4宫格基础模板768套,观看64页内容时,明显看到有错误 【教学类-43-13】 20240103 (4宫格数独:错误版…

如何做好档案数字化前的鉴定工作

要做好档案数字化前的鉴定工作,可以按照以下步骤进行: 1. 确定鉴定目标:明确要鉴定的档案的内容、数量和性质,确定鉴定的范围和目标。 2. 进行档案清点:对档案进行全面清点和登记,包括数量、种类、状况等信…