2023.10.17 关于 wait 和 notify 的使用

news2025/3/13 10:21:16

目录

引言

方法的使用

引入实例(wait 不带参数版本)

wait 方法执行流程

wait 和 notify 组合实例

wait 带参数版本

notify 和 notifyAll 的区别

经典例题 

总结 


引言

  • 线程最大的问题是抢占式执行,随机调度
  • 虽然线程在内核里的调度是随机的,但是可以通过一些api 来控制线程之间的执行顺序,让线程主动阻塞,主动放弃CPU,以便让给其他线程使用

简单示例

  • 有线程t1 和 线程t2,希望线程t1 先干活,等到干的差不多了,再让线程t2 来干活
  • 此时就可以让线程t2 先 wait(阻塞,主动放弃CPU),等线程t1 干的差不多了,再通过 notify 通知线程t2,把线程t2 唤醒,让线程t2 接着干
注意:

join 或 sleep 与 wait 和 notify 之间使用的区别

  • 使用 join ,则表示线程t1 必须要彻底执行完,线程t2 才能运行,如果希望线程t1 先干 一半活,再让线程t2 接着干,此时 join 就不能满足需求
  • 使用 sleep 来指定一个休眠时间,同理我们难以知道线程t1 干一半活所需的具体时间,所以难以满足我们的需求
  • 可以认为 wait 和 notify 涵盖了 join 的用途,但是 wait 和 notify 的使用要比 join 麻烦很多,可以根据实际使用场景来自行选择

方法的使用

引入实例(wait 不带参数版本)

public class ThreadDemo16 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
//        wait 不加任何参数,就是死等,一直等待,直到有其他线程唤醒它
        object.wait();
    }
}

执行结果:

  • 该异常为非法的锁状态异常
  • 锁状态就两种一种是 加锁状态,一直是 解锁状态
  • 关于为啥会出现异常,我们还需了解 wait 方法的执行流程

wait 方法执行流程

  • 先释放锁
  • 进行阻塞等待
  • 收到通知之后,重新尝试获取锁,并且在获取锁后,继续往下执行

修改实例

public class ThreadDemo16 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
//        wait 不加任何参数,就是死等,一直等待,直到有其他线程唤醒它
        synchronized (object) {
            System.out.println("wait 之前");
            object.wait();
            System.out.println("wait 之后");
        }
    }
}

运行结果:

  • 相较于 object 未加锁就调用 wait 方法,而出现锁状态异常的报错
  • 这里先给 object 加上锁,再调用 wait 方法,就能很好的进行阻塞等待,并处于 WAITING 状态
  • 此处阻塞 释放掉了对象 object 的锁,从而其他线程 便可以获取对象 object 的锁

wait 和 notify 组合实例

package Thread;

public class ThreadDemo17 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        Thread t1 = new Thread(() -> {
//            这个线程负责进行等待
            System.out.println("t1: wait 之前");

            synchronized (object) {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1: wait 之后");
        });

        Thread t2 = new Thread(() -> {
//            这个线程负责唤醒
            System.out.println("t2: notify 之前");
            synchronized (object) {
//                notify 务必要获取到锁,才能进行通知
                object.notify();
            }
            System.out.println("t2: notify 之后");
        });

        t1.start();
//        这里添加 sleep 等待1秒
//        是为了能够尽量保证先执行线程t1 再执行线程t2
        Thread.sleep(1000);
        t2.start();
    }
}

运行结果:

注意:

  • 此处的 notify 需和 wait 进行配对
  • 如果 wait 使用的对象和 notify 使用的对象不一致
  • 此时 notify 将不会有任何效果
  • 因为 notify 只能唤醒在同一个对象上等待的线程

  • 虽然这里的代码顺序是先执行线程t1 再执行线程t2
  • 但是由于线程调度的随机性,并不能完全保证一定是先执行线程t1 再执行线程t2 
  • 如果调用 notify 时没有线程在 wait,此时的 wait 是无法被唤醒的那么这种通知就是无效通知,但不会有啥副作用
  • 所以在执行线程t1 之后,先 sleep 等待1秒,再执行线程t2,能很大程度的保证线程t1 先执行 wait 方法

wait 带参数版本

  • 上述代码的 wait 为无参数版本,意味着只要线程t2 不进行 notify,此时线程t1 就会始终 wait 下去,也就是死等
  • 所以 wait 带参数版本便能指定一个等待的最大时间, 能很好的避免死等情况的出现

注意:

  • 虽然 wait 带参数版本,看起来跟 sleep 有点像
  • 都能指定等待时间
  • 都能被提前唤醒,wait 使用 notify ,sleep 使用 interrupt
  • 但是还是有本质差别的,其含义截然不同
  • notify 唤醒 wait,是正常的业务逻辑,并不会有任何异常
  • interrupt 唤醒 sleep 则会先触发中断异常,表示这是一个出了问题的逻辑

notify 和 notifyAll 的区别

  • 当有多个线程等待 object 对象时
  • 有一个线程执行 notify 方法,那么将会随机唤醒一个等待的线程
  • 有一个线程执行 notifyAll 方法,那么将唤醒全部等待的线程,然后这些线程再一起竞争锁

经典例题 

  • 有三个线程,分别只能打印 A、B、C ,控制三个线程固定按照 ABC 的顺序来打印

具体思路

  • 我们可以创建两个对象 object1 和 object2
  • object1 用来控制线程t1和线程t2 的执行顺序
  • object2 用来控制线程t2和线程t3 的执行顺序
  • 让线程t3 wait 阻塞等待对象 object2,直到线程t2 执行 notify 
  • 让线程t2 wait 阻塞等待对象 object1,直到线程t1 执行 notify 
  • 这样便能很好的保证先执行线程t1 ,再执行线程t2,最后再执行线程t3
public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException {
        Object object1 = new Object();
        Object object2 = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println("A");
            synchronized (object1) {
                object1.notify();
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (object1) {
                try {
                    object1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("B");
            synchronized (object2) {
                object2.notify();
            }
        });

        Thread t3 = new Thread(() -> {
            synchronized (object2) {
                try {
                    object2.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("C");
        });

        t2.start();
        t3.start();
        Thread.sleep(500);
        t1.start();
    }
}

运行结果:

注意:

  • 之所以这样安排线程的执行顺序并在执行线程t1 前先等待 0.5秒
  • 是因为能很大程度上避免线程t1 执行 notify 之后,线程t2 还未执行 wait 方法阻塞等待对象 object1 
  • 从而导致线程t1 notify 了个寂寞,便会导致线程t2 一直阻塞等待,出现死锁的情况

总结 

  • wait 和 notify 这两个 api 是用来控制线程之间执行顺序的
  • wait 和 notify 均属于 Object 类的方法

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

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

相关文章

UITesting 界面测试

1. 创建界面测试视图 UITestingBootcampView.swift import SwiftUI/// 界面测试 ViewModel class UITestingBootcampViewModel: ObservableObject{let placeholderText: String "Add name here..."Published var textFiledText: String ""Published var…

CVE-2021-26084 漏洞分析

基础知识 Velocity .vm 结尾的文件一般为Velocity模板文件$action $action 是 velocity 上下⽂中的⼀个变量,⼀般在进⾏模板渲染前会设置到 context ⾥⾯。$action 是当前访问路由对应的具体 Action 类。$action.xxx 表⽰取对应 Action 类的 xxx 属性值 ${} 和 $!…

Kotlin中的字符串基本操作

字符串定义: val str: String "Hello World"val str1 "Hello World"获取字符串的长度: println(str.length)通过索引方式访问某个字符,索引从0开始: println(str[4])通过for循环迭代字符串: for…

Python-Python高阶技巧:闭包、装饰器、设计模式、多线程、网络编程、正则表达式、递归

版本说明 当前版本号[20231018]。 版本修改说明20231018初版 目录 文章目录 版本说明目录Python高阶技巧闭包简单闭包修改外部函数变量的值实现以下atm取钱的闭包实现了闭包注意事项 装饰器装饰器的一般写法(闭包写法)装饰器的语法糖写法 设计模式单例…

微信小程序中如何使用fontawesome6的免费图标

一、官网下载fontawesome6 Download Font Awesome Free or Pro | Font Awesome 二、使用transfer编码成Base64 transfer打开官网:Online font-face generator — Transfonter 首先先把刚刚下载的fontawesome6解压,将文件夹中的字体上传(点…

发电机组负载测试的必要性

发电机组负载测试是确保发电机组能够在实际运行中稳定工作的重要步骤,负载测试可以模拟发电机组在不同负载条件下的工作情况,评估其性能和稳定性。负载测试可以验证发电机组在不同负载条件下的性能表现,通过模拟实际使用情况评估发电机组的输…

【Flutter】第一篇基础:站在一名web前端开发者的角度看代框架

Flutter Flutter 是一个跨平台的 UI 工具集,它的设计初衷,就是允许在各种操作系统上复用同样的代码,例如 iOS 和 Android,同时让应用程序可以直接与底层平台服务进行交互。如此设计是为了让开发者能够在不同的平台上,…

怎么把m4v转换为mp4?

怎么把m4v转换为mp4?M4V是一种由苹果公司开发的视频文件格式,该格式可以在苹果公司的iTunes和QuickTime软件中播放。M4V格式本质上与MP4格式相似,但M4V通常包括了用于数字版权管理(DRM)的保护措施,以控制该…

【笔记-OrCAD】WARNING(ORCAP-36038)解决办法

问题描述: OrCAD16.6绘制好原理图后,点击“*.dsn”文件可以生成网表,在存放原理图的文件内找到allegro文件夹,用记事本打开netlist.log文件,可以看到具体的警告原因,例如: WARNING(ORCAP-36038)…

优雅而高效的JavaScript—— Class 和模块化

😊博主:小猫娃来啦 😊文章核心:优雅而高效的JavaScript—— Class 和模块化 文章目录 引言Class 的概念和用法Class 的定义Class 的继承Class 的静态方法和属性 模块化的概念和用法模块的导出和导入模块的默认导出和命名导出模块的…

SpringCloud: sentinel链路限流

一、配置文件要增加 spring.cloud.sentinel.webContextUnify: false二、在要限流的业务方法上使用SentinelResource注解 package cn.edu.tju.service;import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockExcept…

CVPR、ICCV、ECCV论文获取

CVPR每年召开,ICCV两年一次 链接地址 ECCV两年一开 链接地址

10. 机器学习-评测指标

Hi,你好。我是茶桁。 之前的课程中,我们学习了两个最重要的回归方法,一个线性回归,一个逻辑回归。也讲解了为什么学习机器学习要从逻辑回归和线性回归讲起。因为我们在解决问题的时候,有限选择简单的假设,越复杂的模型…

十七、文件(1)

本章概要 文件和目录路径 选取路径部分片段路径分析Paths 的增减修改 目录 在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。 打开并读取文件对于大多数编程语言来说是非常常用的,由于 I/O 糟糕的设计以至于很少有人能够在不…

第七章 排序

第七章 排序 概述插入排序交换排序冒泡排序快速排序 选择排序直接选择排序堆排序 归并排序有序序列合并二路归并排序 小试牛刀 概述 排序就是将一组对象按照规定的次序(升序或降序等)重新排列的过程,往往为检索服务相同键值的两个记录在排序…

索引背后的数据结构——B+树

为什么要使用B树? 可以进行数据查询的数据结构有二叉搜索树、哈希表等。对于前者来说,树的高度越高,进行查询比较的时候访问磁盘的次数就越多。而后者只有在数据等于key值的时候才能进行查询,不能进行模糊匹配。所以出现了B树来解…

SQL数据库管理工具RazorSQL mac中文版特点与功能

RazorSQL mac是一款功能强大的SQL数据库管理工具,它支持多种数据库,包括MySQL、Oracle、Microsoft SQL Server、SQLite、PostgreSQL等。 RazorSQL mac 软件特点和功能 多种数据库支持:RazorSQL支持多种数据库,用户可以通过一个工…

故障预测与健康管理(PHM)在工业领域的发展前景

故障预测与健康管理(PHM)作为一种关键技术,已经在工业领域引起了广泛的关注和应用。PHM利用传感器、数据科学和智能算法等技术手段,通过实时监测和分析设备和系统的状态,提前发现潜在故障,并采取适当的维修…

制作linux系统内部yum源仓库

需求说明 制作内网linux系统yum源仓库,比较简单的方式就是添加系统镜像,此种yum配置方式可参考文章 https://blog.csdn.net/d1240673769/article/details/108477661 如果无法提供系统镜像,那该如何创建内网的yum源仓库呢?本文提…

互联网Java工程师面试题·Java 总结篇·第六弹

目录 56、TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素? 57、Thread 类的 sleep()方法和对象的 wait()方法都可以让线程暂停执行,它们有什么区别? 58、线程的 sleep()方法和 yield()方法有什…