当前位置: 首页 > news >正文

等待和通知

引入

由于线程是抢占式执行的,因此线程之间的执行的先后顺序难以预知

但是实际开发中我们希望合理协调多个线程之间执行的先后顺序.

这里的干预线程先后顺序,并不是影响系统的调度策略(内核里调度线程,仍然是无序调度).

就是相当于在应用程序代码中,让后执行的线程主动放弃被调度的机会.就可以让执行线程,先把对应的代码执行完了.

完成这个协调工作,主要涉及到三个方法

wait()/wait(long timeout):让当前线程进入准备状态.

notify()/notifyAll():唤醒在当前对象上等待的线程.

注意:wait,notify,notifyAll都是Object类的方法.

wait()方法

一个线程重复拿到锁,别的线程无法拿到锁,这个情况称为"线程饿死/饥饿".属于概率性事件.虽然不像死锁那样严重.这种情况确实是bug.没那么严重,但也极大地影响了程序的运行.

处理:使该线程主动放弃对锁的争夺/放弃去cpu调度执行(进入阻塞,也就是wait).一直到这个条件具备,再解除阻塞,参与锁竞争.

wait做的事情:

1.使当前执行代码的线程进行等待.(把线程放到等待队列中).

2.释放当前的锁.

3.满足一定条件时被唤醒,重新尝试获取这个锁.

其中,1,2条可以让其他线程有机会拿到锁了.第三条指当其它线程调用notify的时候,wait解除阻塞.

wait结束等待的条件:

1.其它线程调用该对象的notify方法.

2.wait等待时间超时(wait方法提供一个带有timeout参数的版本,来指定等待时间)->这是为了防止死等,具有鲁棒性

3.其它线程调度该等待线程的interrupted方法,导致wait抛出InterruptException异常.

观察wait()方法的使用:

public static void main(String[] args) throws InterruptedException {Object locker = new Object();synchronized (object) {System.out.println("等待中");object.wait();System.out.println("等待结束");}
}

这样执行到object.wait()之后就会一直等待下去,那么程序肯定不能这样一直等待下去了.这个时候就需要使用到了另外一个方法以唤醒,也就是notify().

notify方法

notify方法是唤醒等待的线程.

 1.方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁.

2.如果有多个线程等待,则有线程调度器随机挑选出一个呈wait状态的线程.(前提是操作的是同一个锁).并没有"先来后到"

3.在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁.

代码示例:

public class ThreadTest5 {public static Object locker = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1 wait 之前");try {//t1执行起来之后,执行到这,就会先立即释放锁,进入wait方法(释放锁+阻塞等待)locker.wait();System.out.println("t1 wait 之后");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() -> {try {//t2执行起来之后,先进行sleep(3000)(这个sleep操作就可以让t1先拿到锁)//如果先notify虽然不会有副作用(不会出现异常之类的),但是wait就无法被唤醒,逻辑上有问题Thread.sleep(3000);//t2sleep结束之后,由于t1是wait状态,t2就能拿到锁//接下来打印t2notify之前,执行notify操作,这个操作就能唤醒t1(此时t1就从WAITING状态恢复过来了)synchronized (locker) {System.out.println("t2 notify 之前");locker.notify();//但是由于t2此时还没有释放锁,WAITING恢复之后,尝试获取锁,就可能出现一个小小的阻塞,这个阻塞是由锁竞争引起的//t1目前处于BLOCKED状态,但是时间比较短,肉眼看不见System.out.println("t2 notify 之后");}//t2释放锁之后,就可以继续执行t1} catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();}
}

notifyAll()方法

notify方法只是唤醒某一个等待线程.使用notifyAll方法可以一次唤醒所有的等待线程.

但是注意,这些线程在wait返回时,要重新获取锁,就会因为锁的竞争,使这些线程实际上是一个一个串行执行的(谁先谁后拿到锁是不一定的). 

 理解notify和notifyAll

notify只唤醒等待队列中的一个线程.其它线程还是乖乖等着

notifyAll一下全都唤醒,需要这些线程重新竞争锁.

相比之下,还是更倾向于notify.因为notifyAll全部唤醒之后,不好控制

 wait和sleep的对比

其实理论上wait和sleep完全是没有可比性的,因为一个是用于线程之间通信的,一个是让线程阻塞一段时间,唯一的共同的就是都可以让线程放弃执行一段时间.

1.wait可通过notify唤醒,sleep通过Interrupt唤醒

2.使用wait的最主要的目标,一是不知道夺少时间的前提下使用的.所谓的"超时间",就是"兜底"

使用sleep,一定是直到多少时间的前提下使用的,这个操作不因该作为正常业务逻辑(通过异常唤醒,说明程序应该是出现特殊情况了)

3.wait搭配synchronized使用,sleep不需要

4.wait是Object的方法,sleep是Thread的静态方法

相关文章:

  • 智能优化算法应用:基于广义正态分布算法无线传感器网络(WSN)覆盖优化 - 附代码
  • Qt开发学习笔记01
  • Python与ArcGIS系列(十六)重复节点检测
  • ES通过抽样agg聚合性能提升3-5倍
  • 大模型发展对教育领域的巨大影响
  • 一些系统日常运维命令和语句
  • elementUI中的 “this.$confirm“ 基本用法,“this.$confirm“ 调换 “确认“、“取消“ 按钮的位置
  • Hive增强的聚合、多维数据集、分组和汇总
  • sqlmap400报错问题解决
  • go第三方包发布(短精细)
  • ELK企业级日志分析系统
  • Linux文件系统与基础IO
  • C语言第十七集(待修)
  • 微前端介绍
  • cesium学习记录
  • 「面试题」如何实现一个圣杯布局?
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • Bootstrap JS插件Alert源码分析
  • CentOS从零开始部署Nodejs项目
  • Git的一些常用操作
  • JavaScript-Array类型
  • Node 版本管理
  • vue 配置sass、scss全局变量
  • 三栏布局总结
  • 算法系列——算法入门之递归分而治之思想的实现
  • 跳前端坑前,先看看这个!!
  • 积累各种好的链接
  • 组复制官方翻译九、Group Replication Technical Details
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • $GOPATH/go.mod exists but should not goland
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (万字长文)Spring的核心知识尽揽其中
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET Framework .NET Core与 .NET 的区别
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • .NET与java的MVC模式(2):struts2核心工作流程与原理
  • .NET中两种OCR方式对比
  • @JSONField或@JsonProperty注解使用
  • @requestBody写与不写的情况
  • [AIGC] 开源流程引擎哪个好,如何选型?
  • [bzoj4010][HNOI2015]菜肴制作_贪心_拓扑排序
  • [C\C++]读入优化【技巧】
  • [C++核心编程](四):类和对象——封装
  • [codevs 2822] 爱在心中 【tarjan 算法】
  • [HNOI2008]水平可见直线
  • [IOI2018] werewolf 狼人
  • [JavaWeb学习] Spring Ioc和DI概念思想
  • [js]- 两个对象的合并(Object.assign)
  • [leetcode] 103. 二叉树的锯齿形层次遍历