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

前言:

🌈上期博客:【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.IllegalMonitorStateException
	at 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();
        //创建线程1
        Thread 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.niftyadmin.cn/n/5681188.html

相关文章

Reactor 反应堆模式

Reactor 反应堆模式 1、概念 Reactor(反应堆)模式是一种事件驱动的设计模式,通常用于处理高并发的I/O操作,尤其是在服务器或网络编程中。它基于事件多路复用机制,使得单个线程能够同时管理大量并发连接,而…

el-table给列加单位,表头加样式,加斑马纹

<el-table ref"table" class"dataTable" :data"detailList" :header-cell-style"tableHeaderColor" :row-class-name"tableRowClassName" highlight-current-row><el-table-column label"序号" al…

git 基本原理

文章内容来源于视频 举个案例&#xff0c;家族里面有一本记载祖传秘籍的菊花宝典&#xff0c;这本菊花宝典的正本存储在家族祠堂里面&#xff0c;每一个家庭从正本复制一本存在自己家中&#xff0c;称为副本。这个过程称为clone 一个家庭需要再菊花宝典中添加技能&#xff0c…

Django 配置邮箱服务,实现发送信息到指定邮箱

一、这里以qq邮箱为例&#xff0c;打开qq邮箱的SMTP服务 二、django项目目录设置setting.py 文件 setting.py 添加如下内容&#xff1a; # 发送邮件相关配置 EMAIL_BACKEND django.core.mail.backends.smtp.EmailBackend EMAIL_USE_TLS True EMAIL_HOST smtp.qq.com EMAIL…

Vue 技术入门 day1 模版语法、数据绑定、事件处理、计算属性与监视、class和style绑定、条件渲染v-if/v-show、列表渲染v-for

目录 1.Vue 核心 1.1. Vue 简介 1.1.1 介绍与描述 1.1.2 Vue 的特点 1.2 模板语法 1.2.1 模板的分类 1.2.2 插值语法 1.2.3 指令语法 1.2.4 实例 1.3 数据绑定 1.3.1 单向数据绑定 1.3.2 双向数据绑定 1.3.3 MVVM 模型 1.3.4 data与el的2种写法 1.3.5 实例 1.3.…

HTML基础用法介绍一

VS code 如何快速生成HTML骨架注释是什么&#xff1f;为什么要写注释&#xff1f;注释的标签是什么&#xff1f;标题标签段落标签换行标签与水平线标签 (都是单标签&#xff09;文本格式化标签图片标签超链接标签音频标签视频标签 &#x1f698;正片开始 VS code 如何快速生成…

Paddlets时间序列集成模型回测实战:MLPRegressor、NHiTSModel与RNNBlockRegressor

好的,我们继续深入理解代码的每个部分。以下是每个主要模块的详细解释: 1. 导入模块和库 import json import os import glob import pandas as pd from tqdm import tqdm from paddlets.datasets import TSDataset from paddlets.transform import StandardScaler from pa…

C语言开发基础新手快速入门及精通系列学习教程(系统性完整C语言学习笔记整理)

关注我&#xff0c;一起学编程 前言 作为一名拥有多年开发经验的码农&#xff0c;我的职业生涯涵盖了多种编程语言&#xff0c;包括 C 语言、C、C# 和 JavaScript。在这一过程中&#xff0c;我深刻地意识到扎实的基础对于编程学习的重要性&#xff0c;尤其是对于 C 语言…