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

线程的等待与通知

我们知道. 线程在操作系统上是随机调度执行的. 那么如果我们想要控制线程之间某个逻辑执行的先后顺序, 该怎么办呢?  -->  wait和notify能够为我们解决这个问题.

在Java中, wait()方法是Object类提供的一个方法, 当一个线程调用了某个对象的wait()方法时, 该线程会暂停执行,并等待直到另一个线程在该对象上调用notify()或notifyAll()方法, 这通常用于实现线程间的通信.

我们可以让后执行的线程使用wait进行等待, 当先执行的线程执行完某些逻辑之后, 通过notify唤醒对应的wait, 相当于"通知"了后执行的线程, 让后执行的线程继续执行. 

[注]: wait中一定会执行"解锁"的操作, 所以在调用wait()方法时, 一定要将其放到synchronized代码块中 (先加上锁才能谈解锁), 否则程序就会报错. 另外, notify()的执行也需要持有锁资源 (放到synchronized里面), 否则程序也会报错.

一. 单个线程的等待和唤醒

1. 例子

import java.util.Scanner;public class Demo21 {private static Object locker = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1 wait 之前");try {locker.wait(); // 释放锁并进入阻塞状态.} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1 wait 之后");}});Thread t2 = new Thread(() -> {System.out.println("t2 notify 之前");Scanner scanner = new Scanner(System.in);scanner.next(); // 此处用户输入啥都行, 主要是通过这个 next, 构造一个"阻塞"状态.synchronized (locker) { //线程t1执行wait就会将locker释放, 所以这里线程t2可以顺利拿到lockerlocker.notify();}System.out.println("t2 notify 之后");});t1.start();t2.start();}
}

 线程t1是后执行的线程, 执行wait()进入等待状态. 线程t2是先执行的线程, 执行完它的任务之后会执行notify(), 通知线程t1结束等待. 我们看运行结果:

我们可以看到, 当线程t2执行notify通知t1结束线程后, t1立刻结束等待, 执行后面的语句"t1 wait 之后". 

[注意]: wait执行之后, 是要执行3部分内容的: (1) 释放锁资源. (2) 进入等待状态. (3) 收到notify通知之后, 唤醒等待状态, 继续往下执行. 由于这里执行wait会释放锁资源, 所以无法保证wait中执行的指令是"原子的", 所以就有可能出现一种情况: wait释放锁资源之后, 话没有来得及开始等待, 另一个线程就执行notify了. 由于当前线程还没有进入真正的等待状态. 所以它无法接收到notify的通知. 所以也就无法结束等待, 会持续阻塞下去.

wait还有带时间参数的版本: wait(long timeout), 这个版本指定了超时时间, 如果已经达到了最大等待时间还没有接收到notify通知的话, 那么wait会自己结束等待状态, 继续往下执行.

2. wait(long timeout)和sleep(long timeout)的区别

(1) 使用wait的目的是为了唤醒, 而sleep就是固定时间的阻塞, 不涉及唤醒.

(2) wait必须要搭配synchronized使用, 而sleep与加不加锁没有任何关系.

二. 多个线程的等待和唤醒

1. 例子

import java.util.Scanner;public class Demo22 {private static Object locker = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1 wait 之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1 wait 之后");}});Thread t2 = new Thread(() -> {synchronized (locker) {System.out.println("t2 wait 之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2 wait 之后");}});Thread t3 = new Thread(() -> {synchronized (locker) {System.out.println("t3 wait 之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t3 wait 之后");}});Thread t4 = new Thread(() -> {Scanner scanner = new Scanner(System.in);scanner.next(); //阻塞状态synchronized (locker) {locker.notify();locker.notify();locker.notify();}System.out.println("t4 notify 之后");});t1.start();t2.start();t3.start();t4.start();}
}

上述代码三个线程执行三个wait(), t4线程执行三个notify来唤醒这三个线程.

有三个wait应该notify三次, 但是notify多次也可以 (没有任何副作用). 

2. notifyAll()

上面是三个线程等待, 唤醒三次, 那如果有很多个线程等待呢? --> java中提供了一个notifyAll()方法, 用于一次性唤醒全部线程.  

3. 规定执行顺序

public class Demo23 {private static Object locker1 = new Object();private static Object locker2 = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {System.out.print("A");try {Thread.sleep(100); //这里休眠100ms保证是t2先拿到locker1进入wait状态的.} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1) {locker1.notify();}});Thread t2 = new Thread(() -> {synchronized (locker1) {try {locker1.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.print("B");synchronized (locker2) {locker2.notify();}}});Thread t3 = new Thread(() -> {synchronized (locker2) {try {locker2.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("C");}});t1.start();t2.start();t3.start();}}

如上述代码: 创建了两个锁对象locker1和locker2. 其中t2线程拿到locker1并释放进入等待, t3线程拿到locker2并释放进入等待. t1线程打印完"A"之后, 拿到locker1通知线程t2结束等待. t2结束等待之后, 打印"B", 然后拿到locker2, 通知线程t3结束等待. 然后t3打印"C". 所以这个代码的执行结果只能是ABC. (但是如果不加wait和notify的话, 实际的执行情况并不能确定).

 

三. 小结

(1) wait和notify的主要作用是对线程的执行顺序做出干预

(2) wait和notify都是Object类提供的方法.

(2) wait和notify都需要搭配synchronized来使用.

(3) wait方法提供了无参版本, 也提供了带超时时间参数的版本.

(4) 如果有多个线程都处于wait状态, 但是只有一个notify, 那么这个notify会随机唤醒其中一个wait状态的线程.

(5) notifyAll能够唤醒所有正在wait的线程.

(6) 如果notify调用次数超过了wait的次数, 会将全部wait都唤醒. (没有任何副作用).

 好了, 本篇文章就介绍到这里啦, 大家如果有疑问欢迎评论, 如果喜欢小编的文章, 记得点赞收藏~~

 


http://www.mrgr.cn/news/65971.html

相关文章:

  • 影视会员充值接口对接过程中都需要注意些什么?
  • 为什么使用hooks,什么情况下使用hooks
  • 从数据提取到管理:TextIn平台的全面解析与产品体验
  • 程序员日志之DNF手游1023版本活动补充
  • JavaScript函数
  • 本地缓存库分析(四):fastcache
  • 分类算法——支持向量机 详解
  • Linux 实例:VNC 登录输入正确密码后无响应
  • 第七部分:1. STM32之ADC实验--单通道实验
  • opengl和opencl共享时CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR
  • 面对不同的数据源,要解决什么问题
  • AMD显卡低负载看视频掉驱动(chrome edge浏览器) 高负载玩游戏却稳定 解决方法——关闭MPO
  • 2024年华为OD机试真题-查找接口成功率最优时间段-Java-OD统一考试(E卷)
  • 容器迭代器
  • 火爆全网!南卡clip pro耳夹式耳机已售罄!它为何能这么火
  • Mybatis多数据源(一)介绍
  • 数据结构 ——— 计算链式二叉树叶子节点的个数以及计算链式二叉树的高度
  • backtrader下的轮动策略模板,附年化20.6%的策略源码。
  • 连锁餐饮企业-凡塔斯,用千里聆RPA搭建用户评价管理系统,提升门店服务满意度
  • Qt项目实战:银行利息(贷款)计算器
  • druid-multi-tenant-starter将系统改造成多租户系统如此简单
  • 企业邮箱后缀优化:提升邮件送达率的策略!
  • 三周精通FastAPI:32 探索如何使用pytest进行高效、全面的项目测试!
  • W55RP20-EVB-Pico评估板介绍
  • Android camera2
  • 全面提升小程序用户体验,让你的小程序一举夺目