【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.mzph.cn/news/585075.shtml

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

相关文章

Java动态代理机制 代码示例demo

文章目录 JDK动态代理代码实现示例1.定义发送短信的接口2.实现发送短信的接口3.定义一个 JDK 动态代理类4.获取代理对象的工厂类5.实际使用 JDK 动态代理只能代理实现了接口的类CGLIB动态代理代码实现示例1.实现一个使用阿里云发送短信的类2.自定义 MethodInterceptor&#xff…

Java——猫猫图鉴微信小程序(前后端分离版)

目录 一、开源项目 二、项目来源 三、使用框架 四、小程序功能 1、用户功能 2、管理员功能 五、使用docker快速部署 六、更新信息 审核说明 一、开源项目 猫咪信息点-ruoyi-cat: 1、一直想做点项目进行学习与练手,所以做了一个对自己来说可以完成的…

HCIP:rip综合实验

实验要求: 【R1-R2-R3-R4-R5运行RIPV2】 【R6-R7运行RIPV1】 1.使用合理IP地址规划网络,各自创建环回接口 2.R1创建环回 172.16.1.1/24 172.16.2.1/24 172.16.3.1/24 3.要求R3使用R2访问R1环回 4.加快网络收敛,减少路由条目数量,增…

Unity中URP下的添加雾效支持

文章目录 前言一、URP下Shader支持雾效的步骤1、添加雾效变体2、在Varying结构体中添加雾效因子3、在顶点着色器中,我们使用内置函数得到雾效因子4、在片元着色器中,把输出颜色 和 雾效因子混合输出 二、在Unity中打开雾效三、测试代码 前言 我们使用之…

ubuntu20部署Bringing-Old-Photos-Back-to-Life

环境准备: ubuntu20.04 Python 3.8.10 首先将微软的「Bringing-Old-Photos-Back-to-Life」库 clone 到本地: git clone https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life.git cd Face_Enhancement/models/networks/ git clone https:/…

NFC物联网智慧校园解决方案

近场通信(Near Field Communication,NFC)又称近距离无线通信,是一种短距离的高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输交换数据。这个技术由免接触式射频识别(RFID)发展而来,并兼容 RFID,主要用于…

解决VNC连接Ubuntu服务器打开终端出现闪退情况

服务器环境 阿里云ECS服务器 操作系统:Ubuntu 20.0.4 如何使用VNC连接阿里云ECS服务器 1.阿里云官方指导:通过VNC搭建Ubuntu 18.04和20.04图形界面 2.新手入门ECS——ubuntu 20.04安装图形化界面和本地VNC连接 问题描述 使用VNC连接上新申请阿里云服…

github鉴权失败

问题: 如上图所示 git push 时发生了报错,鉴权失败; 解决方案 Settings->Developer settings->Personal access tokens->Generate new token。创建新的访问密钥,勾选repo栏,选择有效期,为密钥命…

鸿蒙HarmonyOS-图表应用

简介 随着移动应用的不断发展,数据可视化成为提高用户体验和数据交流的重要手段之一。在HarmonyOS应用开发中,一个强大而灵活的图表库是实现这一目标的关键。而MPChart就是这样一款图表库,它为开发者提供了丰富的功能和灵活性,使得…

【持续更新ing】uniapp+springboot实现个人备忘录系统【前后端分离】

目录 (1)项目可行性分析 (2)需求描述 (3)界面原型 (4)数据库设计 (5)后端工程 接下来我们使用uniappspringboot实现一个简单的前后端分离的小项目----个…

Spark作业的调度与执行流程

Apache Spark是一个分布式计算框架,用于处理大规模数据。了解Spark作业的调度与执行流程是构建高效分布式应用程序的关键。本文将深入探讨Spark作业的组成部分、调度过程以及执行流程,并提供丰富的示例代码来帮助大家更好地理解这些概念。 Spark作业的组…

[Angular] 笔记 16:模板驱动表单 - 选择框与选项

油管视频: Select & Option (Template Driven Forms) Select & Option 在 pokemon.ts 中新增 interface: export interface Pokemon {id: number;name: string;type: string;isCool: boolean;isStylish: boolean;acceptTerms: boolean; }// new interface…

前端图片适配不同屏幕方案

预备知识: 设备独立像素,以下图的iphone12 Pro为例,390*844表示的就是设备独立像素(DIP),也可以理解为CSS像素 物理像素(设备像素),就是屏幕的分辨率,显示屏就是由一个个物理像素…

QT/C++ 远程数据采集上位机+服务器

一、项目介绍: 远程数据采集与传输 课题要求:编写个基于TCP的网络数据获取与传输的应用程序; 该程序具备以下功能: 1)本地端程序够通过串口与下位机(单片机)进行通信,实现数据采集任务 2)本地端程序能将所获取下位机数据进行保存(如csv文本格式等); 3…

初识隧道代理HTTP:理解基础概念的重要性

嗨,小伙伴们!如果你对网络世界充满好奇,那么这篇文章就是为你准备的。我们将一起踏上一段奇妙的旅程,探索一个叫做“隧道代理HTTP”的新领域。但在这之前,我们需要先穿上“基础概念”的防护服,以免被这个复…

MR实战:实现数据去重

文章目录 一、实战概述二、提出任务三、完成任务(一)准备数据文件1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录 (二)实现步骤1、Map阶段实现(1)创建Maven项目(2)添加相关依赖…

一语道破爬虫,来揭开爬虫面纱

目录 一、爬虫(网络蜘蛛(Spider)) 1.1、是什么: 1.2、学习的原因 1.3、用在地方: 1.4、是否合法: 1.5、后果 案例: 二、应用领域 三、Robots协议 四、抓包 4.1、浏览器抓包 4.2、抓包工具 常见…

从0到1浅析Redis服务器反弹Shell那些事

文章目录 前言Redis服务1.1 特点与应用1.2 安装与使用1.3 语法和配置1.4 未授权访问 反弹Shell2.1 Web服务写入Webshell2.2 Linux定时任务反弹shell2.3 /etc/profile.d->反弹shell2.4 写入ssh公钥登录服务器2.5 利用Redis主从复制RCE2.6 SSRF漏洞组合拳->RCE 总结 前言 …

stm32中的i2c协议

stm32中I2C 文章目录 stm32中I2CI2C 协议简介I2C物理层协议层I2C基本读写过程 **通讯的起始和停止信号****数据有效性****地址及数据方向****响应** STM32的I2C特性及架构**STM32** **的** I2C外设简介STM32 的 I 2C 架构剖析通讯引脚 通讯过程主发送器主接收器 I2C初始化结构体…

【Linux】指令(本人使用比较少的)——笔记(持续更新)

文章目录 ps -axj:查看进程ps -aL:查看线程echo $?:查看最近程序的退出码jobs:查看后台运行的线程组fd 任务号:将后台任务提到前台bg 任务号:将暂停的后台程序重启netstat -nltp:查看服务及监听…