【并发基础】线程的通知与等待:obj.wait()、obj.notify()、obj.notifyAll()详解
目录
〇、先总结一下这三个方法带来的Java线程状态变化
一、obj.wait()
1.1 作用
1.2 使用前需要持有线程共享对象的锁
1.3 使用技巧
二、obj.notify(All)()
1.1 notify() 方法
1.1.1 调用notify()或notifyAll()不会释放线程的锁
1.2 notifyAll() 方法
1.3 使用技巧
三、使用实例
四、wait()/notify()/notifyAll() 为什么定义在 Object 类中?
wait()、notify/notifyAll() 方法都是Object的本地final方法,无法被重写。
〇、先总结一下这三个方法带来的Java线程状态变化
当Java线程调用wait()方法后,该线程会进入等待队列,并且会释放占用的锁资源。线程状态会变为WAITING或TIMED_WAITING。该线程不会被挂起到外存,而是在内存中等待被唤醒。线程等待的条件通常是由其他线程调用notify()或notifyAll()方法来唤醒该线程。
当线程被唤醒时,它会重新尝试获取锁资源并从wait()方法返回。线程状态会变为BLOCKED,直到它获得了锁资源为止。如果成功获取锁资源,线程状态会变为RUNNABLE,然后可以继续执行。如果获取锁资源失败,则线程会继续等待,并且状态会维持在BLOCKED或WAITING或TIMED_WAITING状态,直到它再次被唤醒。
需要注意的是,线程在等待期间会消耗一定的资源,因此应该避免过多的线程等待。另外,线程在等待期间不会占用CPU时间片,因此可以减少CPU的利用率,提高系统的性能。
一、obj.wait()
1.1 作用
wait()是Object里面的方法,Object是所有对象的父类,即所有对象都可以调用wait()方法。wait方法还有可以传入等待时长的,可以让线程等待指定的时间后自动被唤醒。调用wait()会使Java线程进入到WAITING状态,调用wait(long time)会使Java线程进入到TIMED_WAITING状态。(WAITING和TIMED_WAITING状态就是阻塞状态)
当一个线程调用一个共享变量的wait()方法时,该线程会阻塞(等待)。直到发生以下几种情况才会恢复执行:
- 其他线程调用了该共享对象的 notify() 方法或者 notifyAll() 方法(继续往下走)
- 其他线程调用了该线程的 interrupt() 方法,该线程会 InterruptedException 异常返回
等待线程:假设调用的是obj对象的wait()方法,wait的执行线程,也就是被暂停的线程,就称为对象obj上的等待线程。对象的wait方法可能被不同的线程执行,所以同一个对象可能会有多个等待线程。
1.2 使用前需要持有线程共享对象的锁
在使用wait()、notify()和notifyAll()方法方法前,需要先持有锁。如果调用线程共享对象的wait()、notify()和notifyAll()方法的线程没有事先获取该对象的监视器锁,调用线程会抛出IllegalMonitorStateException 异常。当线程调用wait() 之后,就会释放该对象的监视器锁。
使用wait()、notify()和notifyAll()方法方法前,需要先持有锁:
- 表象:wait、notify(ALL)方法需要调用 monitor 对象
- 本质:Java的线程通信实质上是共享内存,而不是直接通信
那么,一个线程如何才能获取一个共享变量的监视器锁?
1、执行synchronized 同步代码块,使用该共享变量作为参数。
synchronized(共享变量) {// TODO
}
2、调用该共享变量的同步方法(synchronized 修饰)
synchronized void sum(int a, int b) {// TODO
}
如下代码示例,线程A与线程B,在线程A中调用共享变量obj的wait()方法,在线程B中进行唤醒notify()。
/*** Object的Wati()方法的使用*/
@Slf4j
public class WaitTest {public static void main(String[] args) {// 定义一个共享变量Object obj = new Object();// 创建线程AThread threadA = new Thread(new Runnable() {@Overridepublic void run() {log.info("线程" + Thread.currentThread().getName()+"开始执行");try {// 获取共享变量的对象锁synchronized(obj){// 线程A 等待log.info("线程" + Thread.currentThread().getName()+"等待");// 调用wait(),线程A阻塞,并且释放掉获取到的obj的对象锁obj.wait();}} catch (InterruptedException e) {e.printStackTrace();}log.info("线程" + Thread.currentThread().getName()+"执行结束");}},"A");// 创建线程BThread threadB = new Thread(new Runnable() {@Overridepublic void run() {log.info("线程" + Thread.currentThread().getName()+"开始执行");// 获取共享变量锁synchronized (obj){// 线程B 唤醒或者中断 调用obj的唤醒操作或者使A线程中断的操作都可以将正在阻塞的A线程唤醒log.info("线程" + Thread.currentThread().getName()+"唤醒");obj.notify(); // 唤醒操作// threadA.interrupted(); // 中断操作}log.info("线程" + Thread.currentThread().getName()+"执行结束");}},"B");// 启动线程AthreadA.start();try {// 等待200ms,让线程B获取资源,在这200ms期间A就被阻塞了,释放了obj对象锁Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}// 启动线程BthreadB.start();}
}
执行结果:
线程A开始执行
线程B开始执行
线程B执行结束
线程A执行结束
可以看到主程序线程A启动之后,休眠了200ms让出cup执行权,线程B开始执行后调用notify()方法对阻塞线程A进行唤醒。
故:当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:(1)其他线程调用了该共享对象的notify()或者notifyAll()方法;(2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。
1.3 使用技巧
- wait()方法一般配合while使用
- 被唤醒后会重新竞争锁,之后从上次wait位置重新运行
- while 多次判断,防止在wait这段时间内对象被修改
二、obj.notify(All)()
notify()和notifyAll()方法也是Object里面的方法,Object是所有对象的父类,即所有对象都可以调用notify()和notifyAll()方法。但是线程中共享变量在调用这两个方法前,该线程需要获取到这个共享变量的锁才可以,否则会抛出异常。
1.1 notify() 方法
一个线程调用共享对象的 notify() 方法后,会唤醒一个在该共享变量上调用 wait(...) 系列方法后阻塞的线程。
通知线程:调用notify/notifyAll方法时所在的线程叫做通知线程。
1.1.1 调用notify()或notifyAll()不会释放线程的锁
当线程调用notify()或notifyAll()方法时,它不会释放掉线程持有的锁。
在Java中,每个对象都有一个相关联的锁,也称为监视器锁。当一个线程需要访问被该锁保护的对象时,它必须先获得该锁的所有权。所以只有获得锁的线程才能调用wait()、notify()和notifyAll()方法。
当线程调用notify()或notifyAll()方法时,它仅仅是唤醒等待在该对象上的一个或多个线程,以便它们可以继续执行。它不会释放线程持有的锁。因此,其他线程仍然无法访问被该锁保护的对象,直到调用notify()或notifyAll()方法的线程释放锁资源。
在多线程编程中,必须小心地管理锁,以避免死锁和竞争条件等问题。通常,为了确保线程安全和避免死锁,必须确保在访问共享资源时只有一个线程持有锁。当然,这也需要合理地使用wait()、notify()和notifyAll()方法来协调线程的执行顺序。
值得注意的是:
- 一个共享变量上可能会有多个线程在等待,notify()具体唤醒哪个等待的线程是随机的;
- 被唤醒的线程不能马上从wait()方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,等到唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行;
1.2 notifyAll() 方法
notifyAll() 方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
1.3 使用技巧
尽量让notify / notifyAll()靠近临界区结束的地方。免得等待线程因为没有获得对象的锁,而又进入等待状态。
三、使用实例
比较经典的就是生产者和消费者的例子。
在生产者消费者模型中,推荐使用notifyAll,因为notify唤醒的线程不确定是生产者或消费者。
public class NotifyWaitDemo {// 共享变量队列的最大容量public static final int MAX_SIZE = 1024;// 共享变量public static Queue queue = new Queue();public static void main(String[] args) {// 生产者Thread producer = new Thread(() -> {// 获取共享变量的锁才能调用wait()方法synchronized (queue) {// 一般wait()都配合着while使用,因为线程唤醒后需要不断地轮循来尝试获取锁while (true) {// 当队列满了之后就挂起当前线程(生产者线程)// 并且,释放通过queue的监视器锁,让消费者对象获取到锁,执行消费逻辑if (queue.size() == MAX_SIZE) {try {// 阻塞生产者线程,并且使当前线程释放掉共享变量的锁queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 空闲则生成元素,并且通知消费线程queue.add();// 唤醒消费者来消费,建议用notifyAll(),因为notify()无法确定会唤醒哪一个线程queue.notifyAll();}}});// 消费者Thread consumer = new Thread(() -> {// 需要先获取锁synchronized (queue) {while (true) {// 当队列已经空了之后就挂起当前线程(消费者线程)// 并且,释放通过queue的监视器锁,让生产者对象获取到锁,执行生产逻辑if (queue.size() == 0) {try {// 阻塞消费者线程,并释放共享对象的锁queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 空闲则消费元素,并且通知生产线程queue.take();queue.notifyAll();}}});// 先执行生产者线程producer.start();try {// 将当前线程睡眠1000ms,让生产者先将队列生产满,然后wait阻塞起来,并且释放持有的锁。为了后续能执行消费者线程Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 执行消费者线程consumer.start();}// 共享变量static class Queue {private int size = 0;public int size() {return this.size;}// 生产操作public void add() {// TODOsize++;System.out.println("执行add 操作,current size: " + size);}// 消费操作public void take() {// TODOsize--;System.out.println("执行take 操作,current size: " + size);}}
}
四、wait()/notify()/notifyAll() 为什么定义在 Object 类中?
由于Thread类继承了Object类,所以Thread也可以调用者三个方法,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在Object类中。
相关文章:【并发基础】一篇文章带你彻底搞懂睡眠、阻塞、挂起、终止之间的区别
【并发基础】Java中线程的创建和运行以及相关源码分析
【并发基础】线程,进程,协程的详细解释
【并发基础】操作系统中线程/进程的生命周期与状态流转以及Java线程的状态流转详解
相关文章:
【并发基础】线程的通知与等待:obj.wait()、obj.notify()、obj.notifyAll()详解
目录 〇、先总结一下这三个方法带来的Java线程状态变化 一、obj.wait() 1.1 作用 1.2 使用前需要持有线程共享对象的锁 1.3 使用技巧 二、obj.notify(All)() 1.1 notify() 方法 1.1.1 调用notify()或notifyAll()不会释放线程的锁 1.2 notifyAll() 方法 1.3 使用技巧 三、使用实…...
css黏性定位-实现商城的分类滚动的标题吸附
传统的黏性定位是使用js通过计算高度来实现的,当元素滚动到一定位置时吸附在当前位置。下面我们通过css来实现黏性定位功能。 黏性定位 黏性定位目前主流的浏览器已经全部支持,顾名思义,黏性定位具有吸附的效果,其实它是positio…...
@Component和@bean注解在容器中创建实例区别
Component和Bean的区别 在Spring Boot中,Component注解和Bean注解都可以用于创建bean。它们的主要区别在于它们的作用范围和创建方式。 Component注解是一种通用的注解,可以用于标注任何类。被标注的类将被Spring容器自动扫描并创建为一个bean。这个bea…...
不写注释就是垃圾
最近Linux6.2出来了增加了很多新的东西,有看点的是,Linux确实要可以在Apple M1上面运行了,这应该是一个很大的新闻,如果有这么稳定的硬件支持,那对于Linux来说相当于又打下了一大片的江山。其中关于Linux6.2的特性罗列…...
深信服一面
1.C变量存储在哪里,生命周期是怎样的 2.静态成员变量和成员函数的特性,在哪里用过吗 3.new和delete是什么,和malloc和free对比有啥优势 4.new能不能重载,重载new有什么用 5.多态是怎么实现的,有什么优势和目的 6.…...
【C语言】深度理解指针(中)
前言✈上回说到,我们学习了一些与指针相关的数据类型,如指针数组,数组指针,函数指针等等,我们还学习了转移表的基本概念,学会了如何利用转移表来实现一个简易计算器。详情请点击传送门:【C语言】…...
步进电机运动八大算法
引导一种模块化(Module)设计思想,将传统步进电机的控制器(controller)、驱动器(Driver)、运动算法(Arithmetic)三合一。 对比国内外步进电机驱动原理和已有工作,结合各种硬件特性,改进或实现了可实际移植并用于步进电机控制八大算法。本产品…...
如果你持续大量的教坏ChatGPT,它确实会变坏
你输出的很多数据是经过人工标注吗,以确保可以正常对外展示出来,而不是有性别歧视、种族歧视或者其它意识形态为多数人所不认同的内容产生? 作为AI语言模型,我并不直接处理或输出任何数据,我的任务是通过对输入的自然语…...
opencv学习(二)图像阈值和平滑处理
图像阈值ret, dst cv2.threshold(src, thresh, maxval, type)src: 输入图,只能输入单通道图像,通常来说为灰度图dst: 输出图thresh: 阈值maxval: 当像素值超过了阈值(或者小于阈值,…...
【含源码】用python做游戏有多简单好玩
有很多同学问我还有其他什么小游戏吗,游戏是怎么做的,难不难。我就用两篇文章来介绍一下,如何使用Python做游戏。 兔子与灌 俄罗斯方块 休闲五子棋 走迷宫 推箱子 消消乐 超多小游戏玩转不停↓ 更多小游戏可以评论区讨论哦,喜欢…...
C++常用函数
std::sort std::sort 函数用于对数组或容器进行排序,可以按照默认的升序排序或指定比较函数进行排序。 语法如下: template <class RandomAccessIterator> void sort(RandomAccessIterator first, RandomAccessIterator last);template <clas…...
Android Framework基础到深入篇
Android Framework基础到深入篇 KernelSU Android上基于内核的Root方案 Android系统源码下载/编译篇...
【Go进阶训练营】聊一下go的gc原理
背景 正好周末时间,就打算梳理以下自己对go gc的理解。跳出语言层面来说,gc分为两种,一种是手动创建,手动销毁。另一种就是由自动分配自动销毁,前者就是c,c的代表,后者就是java,go。 而整个流程…...
英飞凌Tricore原理及应用介绍05_中断处理之中断路由(IR)模块详解
目录 1.概述1.1相关缩写2 TC3xx中IR特性介绍3.SRN(中断服务请求优先级)3.1 寄存器中的各Bit位讲解3.2 如何改变SRN配置4. 实际应用介绍4.1 如何利用SRC寄存器检查OS中断配置是否正确?1.概述 在Tricore架构中允许有多个中断源包括片上外设及外部中断世间产生的中断请求,以打…...
微搭问答002-移动端上传的文件如何在PC端下载
遇到一个问题,就是上传的图片,在手机上可以下载了,但在电脑上怎么下载到电脑 里,包括上传的文件 点击查看页面就可以吧,在企业工作台里 我做了查看页面,小程序可以,但H5和电脑页面不行 你创建一…...
初识JVM
目录 引言 JVM是什么? JVM和java有什么联系? JDK、JRE、JVM有什么区别 为什么学习JVM? JVM——从内存管理开始 运行时数据区域 分区讲解 堆 方法区 程序计数器 本地技术栈 虚拟机栈 对象的创建 指针碰撞: 空闲列表…...
实践分享:Vue 项目如何迁移小程序
最近我们小组刚经历了将成熟的 HTML5 项目转换成小程序,并在app中运行的操作!记录下来分享给各位。 项目:将已有的 Vue 项目转为小程序, 在集成了FinClip SDK 的 App 中运行。 技术:uni-app、FinClip 两个注意事项&…...
JavaScript学习笔记(6.0)
JavaScript类 使用关键字class创建类。 始终添加constructor()方法 class ClassName{constructor(){...} } calss Car{constructor(name,year){this.namename;this.yearyear; } } 创建了一个名为Car的类,并且拥有两个初始属性name和year。 JavaScript类不是对…...
某小公司面试记录
记录一次面试过程,还有一些笔试题,挺简单的,排序,去重,this指向,深浅拷贝,微任务的执行顺序,变量提升等。 ES6数组新增的方法 Array.from: 将两类对象转为真正的数组&am…...
SPI读写SD卡速度有多快?
SD卡是一个嵌入式中非常常用的外设,可以用于存储一些大容量的数据。但用单片机读写SD卡速度一般都有限(对于高速SD卡,主要是受限于单片机本身的接口速度),在高速、实时数据存储时可能会有影响。但具体速度可以达到多少…...
MySQL:索引与事物
目录 简单了解索引的底层数据结构 索引的概念: 索引存在的意义: 索引的使用: 索引实现的数据结构 B树 B 树 B 树的特点 B 树的优势 事物 事物的概念 事物的使用 事物的四大特性 并发可能引起的问题 脏读问题 不可重复读 幻读…...
mybatis实战
目录配置自动下划线驼峰MyBatis解析的SQL和实际传参不符的问题传参是整型,结果是false日期比较入参是字符串入参是Date父子递归查询上下级查询方法一方法二传参数组inmapper中接口注解映射配置 自动下划线驼峰 使用mybatis的自动下划线驼峰转换 mybatis有一个选项…...
【UEFI实战】BIOS与IPMI
KCS KCS全称是Keyboard Controller Style,关于这个名称不用过多的追究,只需要知道它是系统(BIOS和OS)和BMC通信的一种基本方式即可。本文将介绍BIOS下的KCS接口,包括接口使用方式和数据。内容参考自《ipmi-second-gen…...
90%的人都不算会网络安全,这才是真正的白帽子技术【红队】
我敢说,现在网上90%的文章都没有把网络安全该学的东西讲清楚。 为什么?因为全网更多的都是在讲如何去渗透和公鸡,却没有把网安最注重的防御讲明白。 老话说得好:“攻击,是为了更好的防御。”如果连初衷都忘了&#x…...
关于vuex的使用
1.首先安装vuex npm install vuex --save 这时如果直接安装vuex,不指定版本的话,就会直接安装最新的vuex的版本。所以会出现报错。 报错就安装这个 npm install --save vuex3 2.创建文件夹, 有的时候安装好会自动创建vuex的文件夹 …...
第53篇-某商城sign参数分析-webpack【2023-03-07】
声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析三、完整代码一、前言 今天再来试一个webpack的例子吧,网址: aHR0cHM6Ly9tLnlxYi5jb20vYmFuay9…...
探秘MySQL——排查与调优
文章目录一、问题排查一:SQL执行出错二、问题排查二:慢查询0.几个重要参数1.配置慢查询日志命令行配置(重启失效)修改配置文件(永久生效)2.查看慢查询日志3.问题排查1:Look_time耗时4.问题排查2…...
【9.数据页结构】
概述 InnoDB 的数据是按「数据页」为单位来读写的,也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。数据库的 I/O 操作的最小单位是页,InnoDB 数据页…...
演唱会总是抢不到票?教你用Python制作一个自动抢票脚本
人生苦短 我用python 这个大家应该都知道吧? 是中国综合类现场娱乐票务营销平台, 业务覆盖演唱会、 话剧、音乐剧、体育赛事等领域。 如何快速抢票? 那么, 今天带大家用Python来制作一个自动抢票的脚本小程序 本文源码python安…...
【系统开发】WebSocket + SpringBoot + Vue 搭建简易网页聊天室
文章目录一、数据库搭建二、后端搭建2.1 引入关键依赖2.2 WebSocket配置类2.3 配置跨域2.4 发送消息的控制类三、前端搭建3.1 自定义文件websocket.js3.2 main.js中全局引入websocket3.3 App.vue中声明websocket对象3.4 聊天室界面.vue3.5 最终效果一、数据库搭建 很简单的一个…...
企业网站怎么做的更好/seo搜索引擎优化ppt
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不要…...
360百度网站怎么做/seo网络推广技术
大数据Spark运行模式: Yarn模式与配置详解在强大的Yarn环境下Spark是如何工作的?(在国内公司中,Yarn使用的非常多)。1 解压缩文件将spark-3.0.0-bin-hadoop3.2.tgz文件上传到linux并解压缩,放置在指定位置。tar -zxvf spark-3.0.0…...
信息技术用C 做登录界面网站 csdn/建网站建设
影调: 对摄影作品而言,“影调”,又称为照片的基调或调子。指画面的明暗层次、虚实对比和色彩的色相明暗等之间的关系。通过这些关系,使欣赏者感到光的流动与变化。摄影画面中的线条、形状、色彩等元素是由影调来体现的࿰…...
怎样下一本wordpress/品牌搜索引擎服务优化
链表是一种常见的数据结构,它由一个个节点组成,每个节点都有一个数据域和一个指向下一个节点的指针域。链表中的节点并不是连续存储在内存中的,而是存储在稀疏分布的各个位置。 红黑树是一种自平衡二叉查找树,它在保证二叉查找树的…...
那个网站做足球测/建站模板网站
人生态度一般来说主要由()基本要素组成。A、人生认知B、人生情感C、人生意向D、人生目标职业目标包括总体目标和一个个阶段性目标,要善于把总体目标分解成一个个阶段性的目标,职务目标美好的人生价值目标靠()才能化为现实。A、客观条件B、主观条件C、社会…...
软件工程技术学什么/网站性能优化的方法有哪些
效果图 静态图 动态图 代码及详解: 代码很简单,让我们直接来看代码和注释 varying vec2 texcoord;// uniform float iGlobalTime; // uniform vec2 iResolution;...