【多线程】wait()和notify()

news2024/9/22 1:13:04

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. 为什么需要wait()方法和notify()方法?
  • 2. wait()方法
    • 2.1 wait()方法的作用
    • 2.2 wait()做的事情
    • 2.3 wait()结束等待的条件
    • 2.4 带参数的wait方法 —— wait(timeout)
    • 2.5 wait()必须写在 synchronized 代码块里
  • 3. notify()方法
    • 3.1 notify()方法的作用
    • 3.2 notify()方法的用法
    • 3.3 notify()方法必须写在 synchronized 代码块里
    • 3.4 notifyAll() 方法
  • 4. 面试题 —— join()、sleep()方法和wait()方法的对比
    • 4.1 join()和wait()方法的区别
      • 4.1.1 从java包来看
      • 4.1.2 从作用效果来看
    • 4.2 sleep()方法和wait()方法的对比
      • 4.2.1 相同点
      • 4.2.2 不同点

通过之前的学习,我们知道线程的调度是无序的,随机的,但在一定的需求场景下,我们希望线程是有序执行的~
join()方法算是一种控制顺序的方式,但是功效是有限的,所以接下来,本期内容具体介绍两种方法:wait()和notify(),合理的协调多个线程之间的执行先后顺序

1. 为什么需要wait()方法和notify()方法?

举一个栗子~ 假如小万和小丁约好一起去吃自助餐,小丁进去之后发现自己喜欢吃的菜都被拿光了!!!而此时工作人员还没有进行补货~ 发生的一系列故事
在这里插入图片描述
解释说明
这里想要的菜看作是锁,而5个人看作是竞争锁的5个线程,小丁在这个菜面前进进出出,其实并没有实质性地释放锁,但由于这盘菜始终没有工作人员补货,小丁也拿不到自己想要吃的菜,小丁就会陷入忙等,而其它人又竞争不到这盘菜,可看作是线程竞争不到CPU资源,其他人就处于一直在阻塞的状态,也什么事情都干不了

所有线程都可以竞争这个锁,这里就会出现一个极端的情况:线程刚释放锁又是该线程获得锁,发现里面没有货,又释放又进去,一直循环着,而其它线程拿不到锁,处于阻塞状态啥也干不了,导致一个问题 —— 线程饿死

这里引入了一个新的概念—— 线程饿死,通过上述案例,总结为:
线程饿死】指一个或多个线程由于某种原因无法获取所需的执行机会,导致它们无法继续正常执行,从而被阻塞在某个状态,不能完成其任务,这种情况通常是优先级设置不当导致的

上述问题如何解决呢?
有些同学可能会觉得线程有记账信息,可以避免此问题,但实际上,并不能!!!
线程的记账信息其实是一个比较宏观的东西,它需要多个线程多运行一段时间才能生成,对于上面的案例,线程饿死,同一个线程进进出出的情况是一瞬间的事,故线程的记账信息无法解决~

正确:使用wait()和notify()可以有效解决上述问题!!! 上述情况如下:
在这里插入图片描述
解释说明
小丁站在菜面前发现没菜时,就先wait()释放锁,并进行阻塞等待,即暂时不参与CPU调度,不参与锁竞争,当服务员进行补菜,通知notify小丁有菜了,可以去拿菜了,小丁再去重新竞争资源拿到菜,小丁在阻塞等待时,小万和其他3个人,也是要拿这盘菜,但是条件不满足,也是wait()等待就行
wait()】发现条件不满足或是时机不成熟时,线程就先阻塞等待
notify()】其它线程构造一个成熟的条件,就可以唤醒该线程,唤醒后就可以参与所竞争了

协调好多个线程的执行顺序是很重要的,其实在日常生活中,还有很多这样的案例,为了更深刻理解,再比如小丁还喜欢打篮球,球场上的每个人都是独立的"执行流" ,即每个人可当作是一个线程,完成一个具体的进攻得分好几个动作, 需要多个人一起相互配合,必须按照一定的顺序执行,例如1号球员先传球,2号球员拿到球才能扣篮,没拿到球时只能wait()阻塞等待,对应线程中,线程1 先 “传球” 完成自己的任务,通知notify()2号球员已经传球了,线程2 拿到球才能 "扣篮"才能完成自己的任务,是讲究顺序的,wait()和notify()就是解决上述问题的

wait()和notify()的作用即合理的协调多个线程之间的执行先后顺序

2. wait()方法

2.1 wait()方法的作用

作用:让某个线程先暂停下来,等一等

wait()方法的初心就是阻塞等待,让线程进入 WAITING 状态!不过我们要区分开来,不要把概念弄混淆了,
wait()方法导致阻塞,竞争锁也可以导致阻塞,这是两种不同线程进入阻塞的方式!

注意】wait()和notify()是Object的方法,只要是个类对象,不是内置类型(也叫基本数据类型),都可以使用wait()和notify()方法

2.2 wait()做的事情

1)释放当前的锁
2)使当前执行代码的线程进行阻塞等待(即把线程放到等待队列中)
3)满足一定条件时收到通知,被唤醒,同时重新尝试获取这个锁

2.3 wait()结束等待的条件

1)其他线程调用该对象的 notify() 方法(这里强调同一对象)
2)wait()方法等待时间超时 (wait() 方法会提供一个带有 timeout 参数的版本,来指定等待时间,超过这个时间,wait()就会结束)
3)其他线程调用该等待线程的 interrupted() 方法,导致 wait() 抛出 InterruptedException 异常

2.4 带参数的wait方法 —— wait(timeout)

wait() 方法也提供了一个带参数的版本,timeout参数即为指定的最大等待时间
不带参数的wait()即为死等,只有notify()方法能唤醒它
带参数的wait(timeout),则为等到最大时间还没有通知,就自己唤醒自己

2.5 wait()必须写在 synchronized 代码块里

这里需要重要注意: wait()必须写在 synchronized 代码块里!!! 两者必须搭配使用,否则会抛出异常

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

上述代码中,直接调用obj.wait(),并没有进行加锁,运行后,抛出 IllegalMonitorStateException,即非法的锁状态异常,运行结果如下:
在这里插入图片描述
为什么会出现这种情况?
回顾wait()需要做的事情,先进行解锁!!! 如果这把锁都没获取到,就尝试解锁,就会产生异常!(在现实生活中,如果你开门,都没有这把锁,就尝试开锁,东西都没有,咋进行开锁操作捏!所以就会抛出异常)

所以在使用wait()方法前,必须要先进行加锁,就是把wait()写在 synchronized 代码块里面!!!

注意事项同时需要注意,加锁的锁对象必须要和wait()的锁对象是同一个,如果加锁对象和调用wait()对象不是同一个,也会抛出IllegalMonitorStateException 异常!!!

正确使用wait()方法如下:

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

打印结果如下:

在这里插入图片描述
打印结果显示一直在等待中,分析可以得到,在执行到 object.wait() 方法后就一直等待下去,但是程序肯定不能一直这么等待下去呀,这个时候就需要使用到另外一个方法唤醒的方法notify()!!! 下面notify()方法闪亮登场~

3. notify()方法

3.1 notify()方法的作用

作用:把该线程唤醒,使其能够继续执行

3.2 notify()方法的用法

1)notify()方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,同时使它们重新获取该对象的对象锁
2)如果有多个线程等待,则由线程调度器随机挑选出一个呈 wait 状态的线程,并不遵循"先来后到"的原则,仍然是随机的
3)在notify()方法后,当前线程不会立刻释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized 代码块之后才会释放对象锁

在接下来的代码里,我们会更深刻理解以上3点

3.3 notify()方法必须写在 synchronized 代码块里

这里需要重要注意: notify()也必须写在 synchronized 代码块里!!! 两者必须搭配使用,否则会抛出异常

注意事项】同时需要注意,必须先执行wait()方法,然后再执行notify()方法,此时才会有效果,试想一下,如果现在还没有执行wait(),就执行notify()方法,这不就相当于一炮打空嘛~没啥效果!没有起到实际作用,虽然没有额外的副作用,也不会抛出异常,但是代码的功能就不能正确执行了

使用wait()和notify()方法,更好理解wait()和notify()方法的用法,代码如下:

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();

        Thread t1 = new Thread(() -> {
            try {
                System.out.println("wait开始");
                synchronized (obj) {
                    obj.wait();
                }
                System.out.println("wait结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();

        Thread.sleep(1000); //保证t1先启动,wait()先执行

        Thread t2 = new Thread(() -> {
            synchronized (obj) {
                System.out.println("notify开始");
                obj.notify();
                System.out.println("notify结束");
            }
        });

        t2.start();
    }
}

分别创建 t1 和 t2 两个线程,且它们对同一个对象加锁,在此代码中让 t1 线程中执行wait(),t2 线程中执行notify(),先后启动t1和t2线程,观察代码运行结果:

在这里插入图片描述解释说明
1)t1 线程先执行,执行到wait()方法,t1 线程的锁就被wait()方法释放,且 t1 线程自身就阻塞等待了,
2)1s之后 t2 线程开始执行,执行到notify()方法,就会通知 t1 线程,t1 线程被唤醒,继续执行
需要注意的是,notify()方法在 synchronized 代码块内部,因此,只有等 t2 线程释放锁之后,t1 线程才能再竞争到锁,t1 才能继续往下执行,所以先打印的是"notify()结束",再是打印"wait结束"

在上述代码中,虽然是 t1 线程先执行的,但是可以通过wait()方法、notify() 方法的控制,让 t2 线程先执行一部分逻辑,执行完后,t2 线程通过 notify()方法唤醒 t1 线程,使 t1 线程继续往下执行!这正是它们的意义,wait()方法、notify() 方法可以合理的协调多个线程之间的执行先后顺序,使线程执行顺序变得可控起来!

3.4 notifyAll() 方法

notify()方法只是唤醒某一个等待线程,使用notifyAll()方法可以一次唤醒等待同一对象的所有线程

存在这样一个情况:可以有多个线程,等待同一个对象,比如在 t1、t2、t3 线程中,都调用 object.wait()
1)此时在 main 线程中调用 object.notify(),就会随机唤醒上述三个线程中的一个,而另外两个线程仍然是处于 WAITING 状态,
2)但是如果调用 object.notifyAll(),此时就会把上述三个线程全部唤醒,此时这三个线程就会重新竞争锁,再依次执行
注意】此时需要三个线程都wait()等待,再通知,不然又要空打一炮啦

4. 面试题 —— join()、sleep()方法和wait()方法的对比

4.1 join()和wait()方法的区别

4.1.1 从java包来看

join()方法
join()方法在java.lang.Thread 声明
wait()方法
wait()方法在java.lang.Object 声明

4.1.2 从作用效果来看

join()方法
当在 t1 线程中调用了t2.join(),这是让 t1 线程等待 t2 线程全部执行完毕才能再执行,这使得线程之间的执行从"并行"变成"串行"
wait()方法
wait()和notify()方法搭配使用,可以让 t2 线程执行一部分,再让 t1 线程执行一部分,t1 线程执行一部分再让 t2 线程执行一部分…

4.2 sleep()方法和wait()方法的对比

4.2.1 相同点

wait()有一个带参数版本,用来体现超时时间,超过这个时间会被自动唤醒,此时和sleep()方法类似
同时,wait()和sleep()方法都能提前被唤醒

4.2.2 不同点

1)最大的区别:在于两者的初心不同,即设计这个东西到底要解决啥问题的不同
【sleep()】sleep()方法单纯是让当前线程休眠一会
【wait()】wait()解决的是线程之间的顺序控制
2)进一步的,实现或使用上,也是有明显区别的,wait()需要搭配锁使用,而sleep不需要,sleep()方法是让程序按照指定时间短暂休眠让出CPU给其它线程,到时间自动恢复,而不带参数的wait()只有被唤醒后,线程才能重新尝试获得锁,得到锁后才能继续执行

本期内容主要讲解如何在实际中,控制线程调度的顺序~
💛💛💛本期内容回顾💛💛💛
在这里插入图片描述
✨✨✨本期内容到此结束啦~

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

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

相关文章

App UI性能测试 - PerfDog使用全教程

App 性能测试指标: 响应、内存、CPU、FPS、GPU渲染、耗电、耗流等。 PerfDog的性能数据更加全面,所以下面以PerfDog来介绍安装使用流程及测试数据的获取与分析。 官网: PerfDog | 全平台性能测试分析专家 第一步,先访问官网进行注册, 注册好账号后,点击下载PerfDog,下…

微信公众平台、公众号、小程序联动

欢迎来到我的博客,代码的世界里,每一行都是一个故事 🎏:你只管努力,剩下的交给时间 🏠 :小破站 微信公众平台、公众号、小程序联动 如何通过unionid获取到微信公众openid如何根据code获取微信公…

UNDO 表空间使用率高 active段占用高 无对应事务执行

UNDO表空间使用率告警,查看占用情况 active段占比很高 select tablespace_name,status,sum(bytes/1024/1024) mb from dba_undo_extents group by tablespace_name,status;不同状态的含义:**ACTIVE **:有活动事务在使用 Undo,这…

掌握Qt的QThread:深入多线程编程

​ 😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加…

产品推荐| 立錡低耗电器件:线性稳压器、Buck 和 Boost 转换器

想让电池用得更久、利用好它的每一份电力?低静态电流的电源转换器是你的必然选择。立錡深谙电源管理之道,为你备好了低耗电的各种产品,其中包括低压差线性稳压器、Buck 转换器和 Boost 转换器,最低消耗仅有 360nA,是无…

前端面试题35(在iOS和Android平台上,实现WebSocket协议有哪些常见的库或框架?)

在iOS和Android平台上,实现WebSocket协议有许多成熟且被广泛使用的库和框架。下面是一些推荐的选项: iOS 平台 SocketRocket 简介:这是由Facebook开源的库,专门为iOS和Mac OS X设计,提供WebSocket连接的功能。它基于S…

Doris数据库---建表、调整表结构操作

一、简介 本文章主讲创建 Doris 自维护的表的语法,以下为本人最近为数据中台接入doris所踩的坑及其解决方案,欢迎点评。 二、doris建表语法: 官网建表语法网址链接:CREATE-TABLE - Apache Doris 官网建表语法如图所示&#xf…

如何查找指定的node版本

1.打开网站 “Node.js — Run JavaScript EverywhereNode.js is a JavaScript runtime built on Chromes V8 JavaScript engine.https://nodejs.org/2. 3. 4.

运动爱好者的新选择:哈氪聆光气传导耳机,轻巧又安全

平时不管是漫步街头、骑行穿梭,还是乘坐公共交通时,我总是喜欢佩戴耳机,借此隔绝外部的喧嚣,享受音乐的乐趣。在户外使用耳机,我更倾向于选择气传导耳机,它们更符合我的需求,因为这种耳机能让我…

Hi3861鸿蒙开发环境搭建

1.1 安装配置Visual Studio Code 打开Download Visual Studio Code - Mac, Linux, Windows选择下载安装Windows系统的Visual Studio Code。 下载后进行安装。Visual Studio Code安装完成后,通过内置的插件市场搜索并安装开发所需的插件如图所示: 1.2 安…

OpenCV对图片中的水果进行识别计算其面积长度等

本项目所用到的技术有: OpenCV Python的一些库:sys,openpyxl,numpy,PyQt5,PIL 本文可以做一些课程设计的项目 本文为作者原创,转载请注明出处,如果需要完整的代码,可以关注我私信 上面是用到的样例图片,一张…

万字总结GBDT原理、核心参数以及调优思路

万字总结GBDT原理、核心参数以及调优思路 在机器学习领域,梯度提升决策树(Gradient Boosting Decision Tree, GBDT)以其卓越的预测性能和强大的模型解释能力而广受推崇。GBDT通过迭代地构建决策树,每一步都在前一步的残差上进行优…

39 线程库

目录 thread类的简单介绍线程函数参数锁线程交替打印原子性操作库无锁CAS智能指针的线程安全单例模式的线程安全 1. thread类的简单介绍 在c11之前,涉及到多线程问题,都是和平台相关的,如windows和linux下各有自己的接口,这使得…

基于SpringBoot的网上书城管理系统

你好呀,我是计算机学姐码农小野!如果有相关需求,可以私信联系我。 开发语言:Java 数据库:MySQL 技术:Java技术,基于SpringBoot框架 工具:Eclipse,MySQL 系统展示 首…

数图助推朝阳佳惠辽宁华联开启数字化导航、精细化管理新纪元!

近期,辽宁省著名零售企业朝阳佳惠与辽宁华联,秉持创新精神,大胆尝试,在品类空间管理方面推出了创新举措。引入了先进的数图可视化陈列管理系统,通过智能化、直观化的方式优化商品布局。此举不仅大幅提高了商品管理的效…

探索Qt的QVariant:灵活的数据交换机制

😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的…

windows的远程桌面连接docker

1. Docker容器中运行远程桌面服务 (RDP):您的Docker容器需要安装和运行远程桌面服务。通常,远程桌面服务在Windows操作系统上可用。如果您使用的是Linux容器,则需要安装一个支持RDP协议的桌面环境和RDP服务器。 2. 开放RDP端口:通…

【正点原子i.MX93开发板试用连载体验】项目计划和开箱体验

本文最早发表于电子发烧友:【   】【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com)https://bbs.elecfans.com/jishu_2438354_1_1.html 有一段时间没有参加电子发…

Java泛型的定义与运用

泛型 泛型的作用从使用层面上来说是统一数据类型,防止将来的数据转换异常。从定义层面上来说,定义带泛型的类,方法等,将来使用的时候给泛型确定什么类型,泛型就会变成什么类型,凡是涉及到泛型的都会变成确…

飞时达土方计算软件:工程师的得力助手

初识飞时达 飞时达土方计算软件,如同一位默默无闻的工匠,静静地伫立在我的工作台上。它没有华丽的外表,也没有炫目的光环,但它的存在,却如同一盏明灯,照亮了我前行的道路。 初识飞时达,是在一…