线程的休眠与唤醒

news2024/10/4 18:27:24

在前面几篇文章中,已经讲过了join()方法的使用,我们知道它是用来控制线程的执行顺序的。

本篇文章中要讲到的wait()方法和notify()方法是用来控制线程执行顺序的,相比于join(),它能够更精确地控制线程之间的执行顺序,接下来我们就来体会一下。

1. wait() & notify() 简介

  1. wait()叫做等待,调用wait()的线程,就会进入阻塞的状态(WAITING);
  2. notify()叫作唤醒,通过该方法可以唤醒调用了wait()方法而进入阻塞的线程(由WAITNG变为RUNNABLE)

注意事项:这两个方法都是Object类的成员方法,也就是说:当线程t1调用了o1.wait()方法进入阻塞态后,只有使用o1.notify()才可以将t1由阻塞态唤醒为就绪态。

2. wait() & notify() 使用

wait()方法内部的执行过程分为以下几步:

  1. 释放锁
  2. 等待通知
  3. 当通知到达后,就会被唤醒,并且尝试重新获取锁(参与锁竞争)

2.1 wait()的使用

注意事项:wait()的第一步上来就要释放锁,也就意味着:wait()必须在synchronized中使用,并且synchronized的锁对象与调用wait()的对象是同一个。

如果不在synchronized中使用wait()会出现什么问题呢?

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

运行结果:我们发现当程序调用到wait()的时候抛了个异常

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.Demo15.main(Demo15.java:14)

异常的名称为IllegalMonitorStateException,意思就是非法的监视器状态异常,所谓监视器指的就是synchronized(监视器锁)

因此我们需要给这段代码块加锁:

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

运行结果:

wait之前

我们发现程序运行到obj.wait();之后就不继续往下走了,原因是wait()的调用导致main线程被阻塞,由于没有被唤醒,它将一直处于阻塞状态,不参与调度。

通过jconsole也可以查看到main线程当前的状态(WAITING):

要想让该线程被唤醒参与调度,只能通过notify()来实现,接下来就来讲解一下notify()方法。

2.2 notify()的使用

注意事项:

  1. 调用notify()的地方,也得使用synchronized加锁,并且锁对象与调用wait()处的锁对象相同;
  2. 调用wait()的对象必须和调用notify()的对象是同一个;
  3. 当有多个线程等待的时候,notify()是随机唤醒一个线程,notifyAll()则是唤醒所有线程共同竞争锁。

先定义一个用于等待的线程:

public class Demo16 {
    //创建一个对象作为锁对象
    public static void main(String[] args) {
        Object locker = new Object();
        //创建等待线程
        Thread waitThread = new Thread(() -> {
            synchronized (locker) {
                System.out.println("wait开始");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait结束");
            }
        });

在定义一个用于唤醒的线程,并创建唤醒与等待线程:

        //创建一个用来通知的线程
        Thread notifyThread = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入任意内容,开始通知");
            scanner.next();
            synchronized (locker) {
                System.out.println("notify开始");
                locker.notify();
                System.out.println("notify结束");
            }
        });

        waitThread.start();
        notifyThread.start();
    }
}

运行结果:

wait开始
请输入任意内容,开始通知
1
notify开始
notify结束
wait结束

Process finished with exit code 0

2.3 使用场景

这里举两例子,来让大家更好地理解wait()notify(),应该在什么时候使用。

2.3.1 两个线程有一定时序需求

  • 线程1:需要计算一个结果
  • 线程2:需要使用这个结果来完成其他事

线程的执行顺序一定是先执行线程1中计算的代码,才执行线程2中的代码。此时线程2就可以wait()线程1,直到线程1计算出结果再notify(),唤醒线程2。

2.3.2 防止线程饿死

由于系统的随机调度,可能导致某个线程迟迟不被调度上CPU,从而导致线程饿死。使用wait()、notify()可以在一定程度上防止线程饿死。

给大家举个例子,有三个线程t1、t2、t3,他们都是完成去银行取钱的操作;一个线程t4是去完成将钱存入银行的操作。

image.png

但是银行的ATM此时又没有钱,取钱者就算上CPU去执行取钱的操作也就没有什么实质的进展。

image.png

但是他们依然会被系统随机调度上CPU去运行。但由于某些取钱者的某些特性,可能导致存钱者的优先级比取钱者低,然后迟迟无法上CPU运行。取钱者此时又没有什么实质的进展,存钱者这个线程又一直无法分配到CPU资源。

image.png

但是如果将三个取钱线程都加上wait()进入阻塞队列等待,直到存钱者notify()才被唤醒,进入就绪队列等待调度,结果就大不一样了。

取钱者在依次被调度上CPU后,执行到wait()代码,就依次进入了阻塞队列,此时就只有一个取钱者的线程参与调度:

image.png

这样就能保证存钱者线程上CPU上执行完存钱的操作后,取钱者线程被唤醒,进入就绪队列等待,有效避免了线程饿死。

3. Java中三种阻塞状态的唤醒方式

下面的几种状态都会导致TCB进入阻塞队列不参与调度,它们的唤醒方式又有所不同。

  • WAITING: 需要其他线程来唤醒
  • BLOCKED: 在其他线程把锁释放后,由操作系统唤醒
  • TIMED_WAITING: 操作系统会计时,时间到了由操作系统唤醒

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

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

相关文章

云仓酒庄携手中视中州国际传媒 开启央视广告战略合作新征程

近日,云仓酒庄与中视中州(央视代理机构)隆重举行2024-2025年度央视广告战略签约仪式,云仓酒庄副总裁周玄代表云仓酒庄签约。此次合作标志着云仓酒庄在品牌传播和市场营销方面迈出了坚实的一步,将借助央视及多家卫视的强…

LeetCode题目104: 二叉树的最大深度(递归\迭代\层序遍历\尾递归优化\分治法实现 )

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容,和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣! 推荐:数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航: LeetCode解锁100…

数据挖掘实战-基于决策树算法构建银行贷款审批预测模型

🤵‍♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞&#x1f4…

【系统架构师】-案例篇(一)UML用例图

1、概述 用于表示系统功能需求,以及应用程序与用户或者与其他应用程序之间的交互关系。 2、组成 参与者(Actors):与系统交互的用户或其他系统。用一个人形图标表示。用例(Use Cases):系统需要…

字符串取第一个空格之后的所有的值字符串取第一个空格之后的第二个元素的值,不要后面的值

当我们后端返回值可能存在某些特定的值或标识导致返回数据不固定且是空格分割时,我们想取出返回字符串中的某个值,就可以参考下面对这个字符串进行操作提取,当然,如果是别的符号分割开的把下面的空格替换即可 1、字符串取第一个空…

IPD推行成功的核心要素(六)ipd总体规划方案设计

IPD指的是集成产品开发,是一种以市场和客户需求为驱动的规划和开发管理体系。其核心来由自市场,研发,制造,服务,采购等跨部门团队来共同管理整个规格和开发过程,从客户需求、产品规划、任务书、概念形成、产…

java -jar提示jar中没有主清单属性(no main manifest attribute)

目录 传送门前言排查原因问题1-》jdk17和jdk8共存导致idea的maven插件识别报错问题2-》pom.xml中mainClass下面的skip属性是罪魁祸首 其他办法(修改jar包) 传送门 SpringMVC的源码解析(精品) Spring6的源码解析(精品&…

woffice– 内部网和外部网WordPress高端资讯主题

下载地址:https://m.gx.cn/site/3046.html 完全灵活,与最新的WordPress品牌兼容 翻译语言超过15种 使用最新技术设计快速web应用程序 所有这些都以谷歌材料设计为灵感,采用易于定制的设计,给人一种优美的现代感和易于导航的用户…

HCIE学习笔记----OSPF详解

OSPF邻居建立的条件 OSPF建立邻居“41”条件总结 4个一致 一个不一致 1.保证接口的前缀 网络信息一致 2.保证ospf区域号和区域类型一致 3.hello包间隔时间和死亡时间一致 4.认证类型和认证认证信息一致 5.路由器的ID不一致 保证唯一性 一-----OSPF 邻接关系建立过程与状…

文字转语音服务的技术

文字转语音(Text-to-Speech, TTS)技术允许将书面文本转换为口语化的语音。以下是一些提供文字转语音服务的调用接口,开发者在选择文字转语音服务时,应考虑支持的语言、声音类型、音频格式、服务稳定性以及是否支持特定功能&#x…

spring框架定时任务(@Scheduled)

内容: 在spring框架中,scheduled注解是用于声明定时任务的,以最简单的方式来创建定时任务。 注意: 要使用scheduled注解,需要确保已下几点: 1.spring应用程序已经开启了定时任务的开启。需要在配置类&am…

【JavaWeb】网上蛋糕商城后台-客户管理

概念 上文中已讲解和实现了后台管理系统中的订单管理功能,本文讲解客户信息管理功能。 客户信息列表 在后台管理系统的head.jsp头部页面中点击“客户管理”向服务器发送请求 在servlet包中创建AdminUserListServlet类接收浏览器的请求 package servlet;import m…

FebHost:什么是新西兰.NZ域名?

.nz域名是新西兰的顶级域名(TLD)。它是专门分配给新西兰的国家代码顶级域(ccTLD)。以 .nz 结尾的域名用于标识与新西兰或其居民相关的网站、电子邮件地址和其他在线资源。 .nz 域名由新西兰域名委员会管理。负责监督 .nz 域名的注…

驱动丹佛斯比例电磁铁放大器

驱动丹佛斯比例电磁铁是一种用于实现对液压系统连续且精确控制的通电带磁性装置。比例阀由直流比例电磁铁和液压阀两部分组成。其中,比例电磁铁是其核心部件,负责将输入的电信号转换成力和位移输出,从而控制液压阀的工作状态。比例电磁铁通过…

啥是PLCnext?它能干啥?

PLCnext控制器 简单来说,PLCnext是基于Linux操作系统的一个控制器运行时,所以它原生具备的两个特点: 1.原生兼容IEC61131 2.原生兼容Linux各类应用 PLCnext的控制器大概长下面这样(AXC F 1152、2152),通…

在全志H616核桃派开发板上进行PyQt5的代码编写和运行

核桃派本地 在上一节我们通过Qt Designer设计了ui窗口并转换成了Python代码,由于是Python编程,因此我们可以在核桃派开发板打开Python代码进行编程。 在核桃派上推荐使用Thonny来打开编写Python文件, 使用请参考:Thonny IDE。 打开上一节生…

每天认识新职业——网络工程师

一、网络工程师是什么 网络工程师是通过学习和训练,掌握网络技术的理论知识和操作技能的网络技术人员。网络工程师能够从事计算机信息系统的设计、建设、运行和维护工作。相关职业:系统集成工程师、计算机硬件工程师职业其他名称:网络管理员、…

TriCore: 从RTOS内核的角度看CSA

今天尝试从RTOS内核的角度来看看 TriCore 的 CSA。 CSA的细节信息可以参考前一篇文章 TriCore User Manual 笔记 1-CSDN博客 CSA 的全称是 Context Save Area,顾名思义就是专门用来保存上下文的一块存储区域。 既然是上下文使用,那必然要求低延迟&…

VS小知识----qDebug打印中文时乱码

问题:vs在打印中文时乱码 分析解决:编码问题,改为UTF-8试试

2024最新独立版校园跑腿校园社区小程序源码+附教程 适合跑腿,外卖,表白,二手,快递等校园服务

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 后台php,前端uniapp可以二次开 2024最新独立版校园跑腿校园社区小程序源码附教程 测试环境:NginxPHP7.2MySQL5.6 多校版本,多模块,适…