Java多线程基础-7:wait() 和 notify() 用法解析

news2024/11/16 7:52:06

线程之间是抢占式执行的,线程调度是无序的、随机的,因此线程之间执行的先后顺序是难以预知的。但是,实际开发中,有时我们希望合理地协调多个线程间执行的先后顺序。

虽然 join() 算是一种控制顺序的方式,但它毕竟“功效有限”;而 wait() 和 notify() (notifyAll())可以完成线程执行顺序的协调工作。

wait() 说白了就是让某个线程先暂停下来等一等,而notify() 就是把该线程唤醒,让它能够继续执行。wait()和notify()是Object的方法,只要是个类对象,都可以调用。

目录

一、为什么需要 wait() 和 notify() ?

二、方法用法解析

1、wait() 与 notify()

(1)wait() 的作用、结束等待的条件与使用条件

(2)wait() 与 notify() 的使用示例

​​​(3)分析代码运行结果

(4)join() 与 wait() 的区别

(5)带参数的wait方法:wait(timeout)

(6)notifyAll() 唤醒等待同一对象的全部线程

三、wait() 与 sleep()、join() 的区别

1、wait() 和 sleep()

2、wait() 和join()


一、为什么需要 wait() 和 notify() ?

举一个典型的例子:

这里,ATM机就看作是锁,而4位老哥就是竞争锁的4个线程。1号老哥在ATM机进进出出,并没有实质性地释放锁;但由于ATM机中始终没钱,1号老哥也取不了钱。1号老哥陷入忙等,其它老哥竞争不到ATM机(即竞争不到CPU资源),一直在阻塞,也什么事情都干不了。

这里就出现了一个问题:线程饿死。 有些同学可能疑惑,线程不是会有记账信息吗?记账信息不就可以解决问题吗?但实际上,线程的记账信息是一个比较宏观的东西,它需要多个线程多运行一段时间才能生成,而上面线程饿死时1号线程进进出出的情况是一瞬间的事情,因此记账信息也无法解决。

而使用 wait() 和 notify() 就可以有效解决上述问题。如果情况是这样的:

wait():发现条件不满足或时机不成熟时,就先让1号线程先阻塞等待。

notify(): 当其它线程构造了一个成熟的条件时,就可以通知唤醒1号。1号被唤醒后,又可以参与锁的竞争了。

这也类似于篮球场上运动员的活动。每个运动员都是独立的 “执行流”,可以认为是一个 “线程”。而完成一个具体的进攻得分动作,则需要多个运动员相互配合,按照一定的顺序执行一定的动作:线 程1 “传球”, 线程2才能 “扣篮”。要扣篮的运动员需要wait传球的运动员把球传过去,才能扣篮;而传球的运动员把球传过去这一操作就类似于notify;扣篮运动员拿到了球,就结束了wait,可以进行后续的操作即扣篮了。


二、方法用法解析

1、wait() 与 notify()

(1)wait() 的作用、结束等待的条件与使用条件

wait 做的事情:

  1. 释放当前的锁。
  2. 使当前执行代码的线程进入阻塞等待(把线程放到等待队列中)。
  3. 满足一定条件时(收到通知时)被唤醒,同时重新尝试获取这个锁。
wait 结束等待的条件:
  1. 其他线程调用该对象的 notify 方法。
  2. wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本,来指定等待时间)。
  3. 其他线程调用该等待线程的 interrupted 方法,导致 wait 抛出 InterruptedException 异常。

wait() 方法的官方文档解释如下: 

注意wait()的使用条件:wait() 必须搭配 synchronized 来使用,wait()必须写到synchronized代码块里面(notify 方法也必须在synchronized代码块中使用)。脱离 synchronized 使用 wait() 会直接抛出异常。如以下代码所示:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        System.out.println("wait之前:");
        obj.wait(); // 所有可能会引起线程阻塞等到的方法都会抛出 InterruptedException 受查异常
        System.out.println("wait之后:");
    }
}

这个代码中,直接调用了obj.wait()而没有加锁,运行后,抛出了 IllegalMonitorStateException(非法的锁状态异常):

如果还没有获取到锁就尝试解锁,运行后就会抛出非法的锁状态异常。这里的代码抛出该异常正是这个原因:wait()方法内部有一步重要的操作:先解锁,再阻塞等待。

因此,在使用 wait() 前,必须先加锁,把wait()写到synchronized代码块内部。同时,Java也规定调用 notify() 也必须在synchronized代码块中。

并且,加锁的锁对象必须要与调用wait()的锁对象是同一个。如果加锁对象与调用wait()的对象不是同一个,也会抛出 IllegalMonitorStateException 异常。

synchronized (obj) {
    obj.wait(); 
}

(2)wait() 与 notify() 的使用示例

注意:在代码中使用wait()和notify()时,必须先执行wait()再执行notify()才有效。如果还没有wait(),就调用了notify(),此时就相当于一炮打空了。此时wait()的线程无法被唤醒,但代码也不会抛出其他异常(没有额外的副作用,但代码也不能正确执行)。

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

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

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

        t1.start();

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

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

        t2.start();
    }
}

运行结果:

​​​(3)分析代码运行结果

  • 在上述代码中,t1先执行。当t1执行到wait()时候,t1的锁被wait()释放,且t1自身进入阻塞等待状态。
  • 1s后,t2获取到锁并开始执行。当t2执行到notify()时,就会通知t1线程唤醒。
  • 注意,由于notify()是在synchronized的内部,因此只有等到t2释放了锁,t1才能再竞争到锁,并继续向下执行。因此,控制台上先输出“notify结束”,再输出“wait结束”。

在上述代码中,虽然t1是先执行的,但是可以通过wait(),notify()的控制让t2先执行一些逻辑;t2执行完之后,notify()唤醒t1(相当于传球操作),t1再继续向下执行(相当于扣篮操作)。这就体现了所谓“wait和notify让线程执行的顺序更加可控”。 

而wait()的初心,正是为了实现阻塞的效果。只有某一个线程暂时进入阻塞了,线程之间的执行顺序才能发生变化。

并且,wait()阻塞等待会让线程进入 WAITING 状态。

(4)join() 与 wait() 的区别

这里也体现了 join() 与 wait() 作用效果上的一个区别。

join() 只能是让t2线程先执行完,再继续执行t1,此时t1与t2之间一定是串行的。而通过wait()和notify(),可以让t2执行完一部分,再让t1执行……t1执行一部分,再让t2执行……t2再执行一部分,再让t1执行……

(5)带参数的wait方法:wait(timeout)

wait() 方法存在一种重载的可以带参数方法。带参数的方法 wait(long timeout) 可以指定一个等待的超时时间timeout。如果线程的wait的时长大于等于超时时间timeout后还没有别的线程notify它,它就会自己唤醒自己。

(6)notifyAll() 唤醒等待同一对象的全部线程

唤醒操作还有一个notifyAll()。

可能有一种情况:有多个线程,wait着同一个对象。比如,在t1、t2、t3中都调用了object.wait(),此时如果在main中调用的是object.notify(),那么会随机唤醒上述的一个线程,而另外两个线程仍然处于waiting状态;如果是调用了object.notifyAll(),此时就会把上述三个线程都唤醒,然后这三个线程都会重新竞争锁,然后依次执行。

注意,仍然要等到t1、t2、t3的都wait()了再去notify()它们,不然又要“空打一炮”了。


三、wait() 与 sleep()、join() 的区别

1、wait() 和 sleep()

这两个方法最大的区别在于被设计出来的初心(即要解决的问题)不同。wait() 要解决的是线程之间的顺序控制问题,而sleep()只是单纯地让当前线程休眠一会儿。

正因如此,进一步地在使用上也有明显的区别,如wait()必须搭配锁来使用,而sleep()不需要。sleep() 是让程序暂停执行指定的时间并让出CPU给其它线程,当时间到了又会自动恢复运行状态;而wait()只有被唤醒之后,线程才会重新尝试获取锁,获取到了锁才能继续执行。

2、wait() 和join()

join()方法是让线程t1等待线程t2线程完全结束(完成其执行)再执行。它主要起同步的作用,使线程之间的执行从“并行”变成“串行”。也就是说,当我们在线程t1中调用了线程t2的join()方法时,线程执行过程发生改变:线程t1必须等待线程t2执行完毕后,才可以继续执行下去。

二者存在于不同的java包中(wait()方法在java.lang.Object中声明,而join()方法在java.lang.Thread中声明),wait()方法用于线程间通信(notify() 唤醒正在wait()的线程,这样的交互看成是一种通信),而join()方法用于在多个线程之间添加排序:第二个线程需要在第一个线程完全执行完成后才能开始执行。

此外,我们可以通过使用notify()或notifyAll()方法唤醒wait(),但是我们不能中途打破join()所施加的等待。

最后,wait()需要在synchronized代码块中使用,而join()不需要。

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

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

相关文章

4月第3周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年4月17日-4月23日飞瓜数据UP主排行榜(B站平台),通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况,为用户提供B站号综合价值的数据参考,根据UP主成长情况用户能够快速找到运营能力强的B…

【华为机考】模拟题:Words、Vowel、计算字符串重新排列数

前言 刷题之路任重而道远,革命尚未成功,同志仍需努力。由于刷惯了 LeetCode,虽然知道华为机考是需要自己输入输出,也稍稍练了一下,结果真做模拟题的时候,一下子忘了怎么获取字符串了,直接搞了个…

数据降维算法 | Matlab基于局部费歇尔判别(LFDA)的分类数据降维可视化

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 数据降维算法 | Matlab 基于局部费歇尔判别(LFDA)的分类数据降维可视化 部分源码 %--------------------

了解网卡的类型

网卡,即网络接口卡,也叫NIC卡,是一种允许网络连接的计算机硬件设备。网卡应用广泛,市场上有许多不同种类,如PCle网卡,服务器网卡。本文将对网卡的基础,功能,元件与类型进行全方位讲解…

用友自主研发企业商用版TimensionDB时序数据库重磅发布!

2023年4月19日,用友BIP技术大会上,用友自主研发专用企业服务能力的商用版时序数据库——TimensionDB重磅发布!源于物联网、工业4.0等场景实践,打造自主、安全、可控的专业服务工业企业的轻量级、高性能、易使用的时序数据管理引擎…

linux-02-软件安装-centos7配置jdk、tomcat、lrzsz、项目部署(Git、Maven)

文章目录 Linux-Day02课程内容1. 软件安装1.1 软件安装方式1.2 安装JDKshell脚本里写 cd命令不生效 1.3 安装Tomcat1.3.1 Tomcat安装好多方便的自定义命令:1.3.2 Tomcat进程查看1.3.3 防火墙操作1.3.4 停止Tomcat 1.4 安装MySQL1.4.1 MySQL安装1.4.2 MySQL启动1.4.3 MySQL登录1…

PYQT5学习笔记04——QObject对象和属性名称设置API以及对应案例

一、QObject对象和属性名称设置API 简单介绍一下PyQt5最上层父类QObject的一些属性方法,并给出具体的使用过程示例。 1、setObjectName(“唯一名称”)和objectName() setObjectName方法的作用是给Qt对象设置一个名称,一般这个名称是唯一的,这…

Excel技能之实用技巧,高手私藏

今天来讲一下Excel技巧,工作常用,高手私藏。能帮到你是我最大的荣幸。 与其加班熬夜赶进度,不如下班学习提效率。能力有成长,效率提上去,自然不用加班。 消化吸收,工作中立马使用,感觉真不错。…

【Java】Eclipse如何创建java项目并运行

前面Eclipse、JDK的安装与JDK环境变量配置好了之后,开始进行基本的使用 一、创建java项目并运行 1、先打开Eclipse IDE 2、创建项目 点击左上角的File—>New—>Project,这样就可以新建一个Java的项目了。也就是说,在Eclipse中&#…

设置ubuntu开机启动脚本

一、新建xx-setpower.service文件 sudo gedit /etc/systemd/system/xx-setpower.service [Unit] DescriptionXX SetPower Service Afternetwork.target Wantsnetwork.target[Service] Typesimple PIDFile/run/take_up_boot.pid ExecStart/usr/bin/take_up_boot.sh Restarton-…

二进制码与格雷码的相互转换原理与Verilog实现

二进制码与格雷码的相互转换原理与Verilog实现 一、写在前面二、二进制码与格雷码的相互转换2.1 二进制码转格雷码原理2.2 异或运算的特性2.3 格雷码转二进制码原理 三、二进制码与格雷码相互转换的Verilog实现四、仿真验证五、写在后面 一、写在前面 二进制码是一种基于二进制…

RibbitMQ之RabbitMQ的介绍与安装

RabbitMQ的介绍与安装 1. MQ 的相关概念1.1 MQ 的概念1.2 MQ 的作用1.3 MQ 的分类1.3.1 ActiveMQ1.3.2 Kafka1.3.3 RocketMQ1.3.4 RabbitMQ 1.4 MQ 的选择 2. RabbitMQ2.1 RabbitMQ 的概念2.2 RabibitMQ 的四大核心2.3 RabbitMQ 的核心部分2.4 RabbitMQ 名词介绍2.5 安装 Rabbi…

数学建模第五天:数学建模算法篇之层次分析法AHP

目录 一、前言 1、例题 2、层次分析法用途 二、层次分析法步骤 1、建立层次结构图 2、构造两两比较矩阵 3、确定相对权重向量 ①特征根法 ②和法 4、一致性检验 5、计算层次总排序权值和一致性检验 一、前言 1、例题 有大学生刘昊、小王、阿三、蛋仔四人&#xff0…

闪电加密和隐藏加密文件夹忘记密码怎么办?

闪电加密和隐藏加密是文件夹加密超级大师的两种文件夹加密类型,它们都拥有超快的加密算法,可以瞬间加密超大文件夹。那么这两种文件夹加密类型忘记密码该怎么办呢?下面我们一起来了解一下吧。 闪电加密和隐藏加密文件夹找回密码 首先&#x…

HashMap的扩容机制、初始化容量大小的选择、容量为什么是2的次幂

前置知识 先来看看HashMap中的成员属性 解释: size当前的容器中Entry的数量,也就是当前K-V的数量loadFactory装载因子,用来衡量HashMap满的程度,loadFactory的默认值是0.75threshold临界值,当实际KV数量超过threshol…

ChatGPT实现语义分析情感分类

语义分析情感分类 我们从开源社区找到了中科院谭松波博士整理的携程网酒店评论数据集(https://raw.githubusercontent.com/SophonPlus/ChineseNlpCorpus/master/datasets/ChnSentiCorp_htl_all/ChnSentiCorp_htl_all.csv)。一共七千余条数据,包括 label 和 review …

Domino中的时间日期格式设置

大家好,才是真的好。 今天我们谈管理运维问题,相对于前面的技术来说,会简单很多。 就是Domino服务器运行时的时间日期显示问题。这和你的邮件、应用等里面的文档时间日期也有关系。 简单来说,在默认的设置情况下,Do…

java——最小的K个数

题目链接 牛客在线oj题——最小的K个数 题目描述 给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。 数据范围:0≤k,n≤10000&…

【刷题之路】LeetCode 206. 反转链表

【刷题之路】LeetCode 206. 反转链表 一、题目描述二、解题1、方法1——改变指针方向1.1、思路分析1.2、代码实现 2、方法2——头插到新链表2.1、思路分析2.2、代码实现 一、题目描述 原题连接: 206. 反转链表 题目描述: 给你单链表的头节点 head &…

4月26号软件更新资讯合集....

Tpflow V7.0.2,PHP 工作流引擎新版发布 ​欢迎使用 Tpflow V7.0.1 工作流引擎 TpFlow 工作流引擎是一套规范化的流程管理系统,基于业务而驱动系统生命力的一套引擎。彻底释放整个信息管理系统的的活力,让系统更具可用性,智能应用型…