【JavaEE初阶】深入理解wait和notify以及线程饿死的解决

devtools/2024/12/23 1:30:50/

前言:

🌈上期博客:【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题-CSDN博客

🔥感兴趣的小伙伴看一看小编主页:【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题-CSDN博客

⭐️小编会在后端开发的学习中不断更新~~~  

🥳非常感谢你的支持

 

目录

📚️1.引言

 📚️2.wait和notify

2.1wait作用

 2.2使用场景和线程饿死

 2.3wait如何使用

1.wait的锁对象

2.wait必须在synchronized内

3.调用wait的锁对象

4.notify的使用

📚️3.理解执行顺序

📚️4.注意事项

4.1wait不同的方法

4.2notify唤醒wait

4.3wait和sleep的区别

📚️5.总结


 

📚️1.引言

 OK啊!!!小伙伴们,在上期继小编讲解过死锁的问题后,本期将开始理解wait和notify的线程问题,那么废话不多说,直接步入正题,go go go~~~;

且听小编讲解,包你学会!!! 

 📚️2.wait和notify

2.1wait作用

这里的作用和上期讲解过的join有异曲同工之妙,都是在多线程随机调度中,通过引入wait和notify来实现干预不同线程的执行顺序,让后执行的线程不被调度,让先执行的线程把对应的代码执行完

 2.2使用场景和线程饿死

例如一个现实场景:取钱问题~~~

这里的ATM可能是没有钱的,那么此时当一个人去取完钱后,解锁后,其他滑稽老铁进入锁的竞争,但是此时刚刚取钱的滑稽老铁也会参与到锁的竞争,就导致此时壹号滑稽老铁由于竞争,和ATM没有钱,而一直反复获取锁,而导致其他的滑稽不能拿到锁,这就是“线程饿死 

注意:由于已经拿到锁的滑稽老铁会处于RUNNABLE状态,但是其他线程处于阻塞状态,那么此时就是“近水楼台先得月” ,其他线程需要进行唤醒,才能参与到锁的竞争,那么就会导致壹号滑稽老铁更容易获取到锁~~~

此时就要运用到wait和notify了,让线程满足条件进行加锁执行,若不满足条件,那么就进入wait,直到满足条件后被notify唤醒~~~

下面是一个伪代码

java"> public static void main(String[] args) {while (true){synchronized (){if (ATM中有钱){进行取钱的操作 break;}else {进入等待;wait(){直到有钱然后被唤醒}}}}}

所以这就是我们所需要看到的情况;

注意:wait在这里做了三件事情

1.释放锁,让其他线程获取锁

2.让线程进入阻塞状态

3.被notify唤醒后,重新参与锁的竞争

 2.3wait如何使用

1.wait的锁对象

这里和加锁是一样的都是需要锁对象,当然这里的锁对象是可以为任何对象的,小编就不再过多解释

2.wait必须在synchronized内

此时如果我们将wait写到synchronized之外,代码如下:

java"> public static void main(String[] args) throws InterruptedException {Object object = new Object();System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}

输出打印日志:

java">wait 之前
Exception in thread "main" java.lang.IllegalMonitorStateExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at thread.testDemo17.main(testDemo17.java:7)

可以看到此时程序报错了;

注意:wait使用的前提是存在锁,所以在使用wait之前线程必须先获取锁。

3.调用wait的锁对象

在调用wait的锁对象必须和加锁的synchronized是同一个锁对象,所以wait解锁是synchronized的锁,重新唤醒后加锁也是synchronized的锁,代码如下:

java">public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}}

此时输出就只有wait之前,因为此时线程进入阻塞了,咱们打开jconsole可以看出到此时的线程状况,如图所示:

此时代码进入WAITING状态~~~

4.notify的使用

注意此时,notify使用与其他的线程,并且调用notify的锁对象,也必须和调用wait的锁对象必须是一样的,唤醒的即对应锁对象调用的wait方法的线程;

📚️3.理解执行顺序

这里需要注意,在使用wait和notify后的代码是如何进行执行的,代码实例如下:

java">public static void main(String[] args) {Object lock=new Object();//创建线程1Thread t1=new Thread(()->{synchronized (lock){System.out.println("wait之前");try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait之后");}});Thread t2=new Thread(()->{try {Thread.sleep(1000);//保证线程1能够成功加上锁} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lock){System.out.println("notify之前");lock.notify();System.out.println("notify之后");}});t1.start();t2.start();}

注意:小编这里为了保证线程1成功加上锁,那么在执行线程2时,就会进入休眠;还有wait和sleep一样,都有可能会被interrupt提前唤醒,所以这里在写wait时,需要进行异常抛出;

输出结果:

那么此时的执行顺序就是如下的:

1.首先线程1开始执行,加锁后执行打印“wait之前”,然后进入等待状态,释放锁并进入阻塞状态;

2.线程2开始执行,获取到锁后打印“notify之前”,然后唤醒线程一;

3.由于线程1处于WAITING状态,被唤醒后,由于锁的竞争会处于一个小小的阻塞,在获取锁这个阶段需要时间开销,所以先打印“notify之后”

4.最后线程1获取到锁后,最后执行最后的打印“wait之后”

📚️4.注意事项

4.1wait不同的方法

在进行wait方法的调用时,可以看到有三个其他的版本,如下图:

第一种:即死等,若没有进程进行唤醒操作,此时就会导致这个线程不再执行

第二种:即超时时间,单位为ms,若没有线程唤醒,那么到了这个时间段,就不再进行等待了;

第三种:即纳秒级别的时间,由于系统精度,可能无法准确执行,且很少使用;

4.2notify唤醒wait

1.若两个锁对象调用的这两个方法,如果锁对象是不一样的,那么就无法进行唤醒;

2.若右两个锁对象调用wait,那么调用notify的锁对象,要唤醒对应的wait,若两个wait的锁对象是一样的,那么随机唤醒

3.notifyAll用于唤醒对应线程上的所有线程;注意:被唤醒的线程中,多个wait的执行,导致锁的竞争,那么此时那个限制性,那个后执行是不确定的;

4.3wait和sleep的区别

1.这里的wait也是带有时间的超时时间的wait方法,sleep也是一样的带有时间的,那么就是到时间就会继续执行;

2.wait和sleep虽然时间没有到,但是任然可以被提前唤醒;wait是通过notify进行唤醒,而sleep是通过interrupt进行提前唤醒;

3.使用wait,是不知道等待的时间的前提下使用的,所谓的超时时间只不过是一个“兜底时间”,而sleep是要知道时间的前提下才使用,虽然也能够被提前唤醒,但是这个是一个不正常的业务流程,(异常唤醒,这是说明代码出现了BUG了)

📚️5.总结

💬💬小编本期讲解了关于wait的使用方法,对应的notify的操作,以及线程饿死的场景演示;当然还有在使用wait和notify时的使用注意事项,还附上了对应代码供uu们参考参考~~~

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

                                               😊😊  期待你的关注~~~


http://www.ppmy.cn/devtools/118664.html

相关文章

代码随想录打卡Day46

今天是动态规划的最后一天,终于要结束折磨了!!!今天的两道题,第一道看视频做的,第二道自己AC的,开心!!!! 647. 回文子串 这道题不能用一维dp数组…

visual studio 调试技巧

visual studio 调试技巧 概述 在使用visual studio 进行调试的时候,有几个调试方法很好用,这里做一些记录。 GTEST 单元测试 参考 VS2022创建C C GTEST工程 - Hello-FPGA - 博客园 (cnblogs.com) 内存查看 命令行测试动态库 附加到进程调试动态库 …

❤ Node实现图片存储和文件存储

# Node13-图片存储接口本地 1、编写错误中间件​ 需要编写一个错误中间件,用来抛出错误,防止因为错误而造成接口崩溃 注意:错误中间件一定要放在所有路由之后 (1) 在所有路由之后放置中间件​ js app.use((err, …

软件架构设计师教程 第11章 11.4 边缘计算概述 笔记

11.4 边缘计算概述 ★★☆☆☆ 11.4.1 边缘计算概念 边缘计算将数据的处理、应用程序的运行甚至一些功能服务的实现,由 网络中心下放到网络边缘的节点上。在网络边缘侧的智能网关上就近采集并且处理数据,不需要上传原生数据。 11.4.2 边缘计算的定义 1…

VMware ESXi 8.0U3 macOS Unlocker OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布

VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 2.7 Dell HPE 定制版 9 月更新发布 VMware ESXi 8.0U3 macOS Unlocker & OEM BIOS 2.7 标准版和厂商定制版 ESXi 8.0U3 标准版,Dell (戴尔)、HPE (慧与)、Lenovo (联想)、IEIT SYSTEMS (浪潮信息)、Cisco (思…

美畅物联丨GB/T 28181系列之TCP/UDP被动模式和TCP主动模式

GB/T 28181《安全防范视频监控联网系统信息传输、交换、控制技术要求》作为我国安防领域的重要标准,为视频监控系统的建设提供了全面的技术指导和规范。该标准详细规定了视频监控系统的信息传输、交换和控制技术要求,在视频流传输方面,GB/T 2…

论文笔记——Graph Bottlenecked Social Recommendation

文章地址 代码地址 1.1简介 随着社交网络的出现,社交推荐已经成为个性化服务的重要技术。最近,基于图的社交推荐通过捕捉高阶社交影响显示出了有希望的结果。大多数基于图的社交推荐的经验研究直接将观察到的社交网络纳入公式,并基于社交同…

随笔 JVM本质

为什么JVM可以被称为机器 (Machine) “学而时习之不亦说乎”,今天我们借着讨论标题这个问题,将JVM的全貌展示出来。 如果你是一名Java Coder,那么你一定听说过 “Write Once, Run Anywhere”,翻译过来就是一次编写到处运行。可能以现在的眼…