<JavaEE> 多线程编程中的“等待和通知机制”:wait 和 notify 方法

目录

一、等待和通知机制的概念

二、wait() 方法

2.1 wait() 方法的使用

2.2 超时等待

2.3 异常唤醒

2.4 唤醒等待的方法

三、notify() 方法

四、notifyAll() 方法

五、wait 和 sleep 的对比


一、等待和通知机制的概念

1)什么是等待和通知机制?

线程是抢占式执行的,无法预知线程之间的执行顺序。

但有时程序员也希望能合理协调多个线程的执行顺序。

因此,在 Java 中使用了等待(wait)和通知(notify)机制,用于在应用层面上干预多个线程的执行顺序

应当注意的是,干预执行顺序并不是干预系统的线程调度策略(操作系统内核中的线程调度仍是无序的),而是使被指定的线程,主动放弃被系统调度的机会,直到其他线程对被指定的线程发出通知,这个线程才再次参与系统的线程调度。(排队都不排了,自然也就轮不到它了)

2)使用等待和通知机制主要涉及以下三个方法
wait()让线程进入等待状态。
notify()唤醒在当前对象上等待的一个线程。
notifyAll()唤醒在当前对象上等待的所有线程。
以上三个方法都是 Object 类的方法。

二、wait() 方法

2.1 wait() 方法的使用

1)wait() 方法需要配合 synchronized 关键字使用

wait() 方法必须在 synchronized 修饰的代码块或方法中使用,否则会抛出 IllegalMonitorStateException 异常。

2)使用锁对象调用 wait() 方法

虽然,wait() 是 Object 类的方法,任何对象都可以调用该方法。

但是为了实现等待通知机制,要求调用 wait() 的对象必须是锁对象,且这个锁对象要与 synchronized 指定的锁对象一致。

3)wait() 方法具体做了什么?

wait() 方法主要执行了以下三个操作:

<1>释放当前的锁。
<2>使当前线程进入等待队列。
<3>通过某些条件被唤醒时,重新尝试获取当前锁。

代码演示wait()使用方法和使用结果:

    public static void main(String[] args) throws InterruptedException {
        //创建锁对象;
        Object locker = new Object();
        
        System.out.println("wait前");
        //在 synchronized 代码块中调用 wait 方法;
        synchronized (locker){
            // wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;
            locker.wait();
        }
        System.out.println("wait后");
    }

//运行结果:
wait前
...

程序没有执行完毕,线程一直在 wait 。

2.2 超时等待

有时间限制的等待

上文中的代码,除非有其他线程唤醒,否则执行后会一直处于 wait 的状态,这就使得程序陷入了“停摆”状态。

在部分场景中,我们可以使用带时间参数的 wait() 方法来规避这个问题。即使没有其他线程唤醒 wait ,wait 仍会在超过规定时间后,自动唤醒,避免了程序的“停摆”。

代码演示带时间参数的wait()使用方法和使用结果:

    public static void main(String[] args) throws InterruptedException {
        //创建锁对象;
        Object locker = new Object();

        System.out.println("wait前");
        //在 synchronized 代码块中调用 wait 方法;
        synchronized (locker){
            // wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;
            locker.wait(3000);
            //3秒后线程被唤醒;
        }
        System.out.println("wait后");
    }

//运行结果:
wait前
wait后

成功执行完毕。

2.3 异常唤醒

代码演示通过抛出异常唤醒wait:

    public static void main(String[] args) {
        //创建锁对象;
        Object locker = new Object();

        Thread t1 = new Thread(()->{
            System.out.println("wait前");
            //在 synchronized 代码块中调用 wait 方法;
            synchronized (locker){
                // wait 方法是由锁对象调用的,调用后,线程释放锁,进入等待队列;
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //3秒后线程被唤醒;
            }
            System.out.println("wait后");
        });

        t1.start();

        //抛出异常,清除中断标志,唤醒t1线程。
        t1.interrupt();
    }

//运行结果:
wait前
wait后
java.lang.InterruptedException

抛出了InterruptedException后,线程被唤醒,继续执行。

2.4 唤醒等待的方法

唤醒等待的方法有三种:
<1>

超时等待:超过了 wait() 方法指定的等待时间。

<2>异常唤醒:通过其他线程调用该等待线程的 interrupted() 方法,抛出异常唤醒。
<3>notify() 方法:其他线程调用该对象的 notify() 方法。

三、notify() 方法

1)notify() 方法有什么作用?

notify() 方法可以唤醒等待的线程。

2)notify() 也要写在 synchronized 修饰的代码块或方法中

notify() 方法也是 Object 类的方法,所以任何对象都可以调用。

在操作系统的原生 API 中,也有 wait() 和 notify() 方法。与 wait() 方法不同,操作系统的原生 API 没有要求 notify() 必须在 synchronized 修饰的代码块或方法中使用。

但是,应注意,在 Java 中还是特别约定了 notify() 方法也是要放在 synchronized 修饰的代码块或方法中的。

3)wait() 和 notify() 方法是通过锁对象联系的

一个锁对象调用的 wait() 只能被同一个锁对象调用的 notify() 唤醒。

如果唤醒时,同一个锁对象有多个线程正在等待,此时只会随机唤醒一个。

4)执行 notify() 方法后,锁在什么时候释放?

在 notify() 方法后,当前线程不会马上释放锁对象,而是等到线程执行完 notify() 方法所在的代码块或方法后,才会释放锁对象。

这也是 Java 中约定 notify() 方法要放在 synchronized 修饰的代码块或方法中的原因。

代码演示通过 notify 唤醒wait:

    public static void main(String[] args) throws InterruptedException {
        //创建一个锁对象;
        Object locker = new Object();

        //创建一个线程;
        Thread t1 = new Thread(()->{
            System.out.println("wait前");
            //打印"wait前"后等待;
            synchronized (locker){
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            //被唤醒后打印"wait后";
            System.out.println("wait后");
        });
        t1.start();

        //休眠两秒,保证t1线程进入等待状态。
        Thread.sleep(2000);
        //打印"notify前"后唤醒;
        System.out.println("notify前");
        synchronized (locker){
            locker.notify();
            //在出代码块前,打印"notify后"。
            System.out.println("notify后");
        }
    }

//运行结果:
wait前
notify前
notify后
wait后

打印"wait前"之后进入阻塞等待,直到被notify唤醒之后才打印了"wait后"。

四、notifyAll() 方法

notifyAll() 方法有什么作用?

唤醒这个锁对象上所有等待的线程。

有多个线程使用同一个锁对象 wait ,当对这个锁对象使用 notifyALL() 方法时,所有在等待的线程都会唤醒。

但是需要注意,在唤醒之后,由于需要重新获取锁,此时被唤醒的线程必然要进行锁竞争,所以这些被唤醒的线程并不是同时就开始执行各自的代码了,而仍然是有先后顺序的执行,顺序依旧是随机的。


五、wait 和 sleep 的对比

相同点
都会使线程阻塞等待。
不同点wait() 方法sleep() 方法
用途用于线程间通信。用于线程阻塞等待。
用法

是 Object 类中的方法,

需要在被 synchronized 修饰的代码块或方法中使用。

是 Tread 类中的静态方法,

方法的使用与 synchronized 无关。

状态

被调用后,当前线程进入 BLOCK 状态并释放锁。

被调用后,当前线程进入 TIME_WAIT 状态。
唤醒

通常通过 notify 唤醒;

可以通过超时或抛出异常唤醒;

通常按设定的时间唤醒;

可以通过抛出异常唤醒;


阅读指针 -> 《经典设计模式之 -- 单例模式(“饿汉模式”和“懒汉模式”实现单例模式)》

<JavaEE> 单例模式的两种实现:“饿汉模式”和“懒汉模式”-CSDN博客介绍了什么是单例模式和单例模式的两种实现模式。重点介绍了单例模式中,“懒汉模式”在多线程下的实现。https://blog.csdn.net/zzy734437202/article/details/134785459

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

若依框架的搭建

若依框架 若依框架的搭建&#xff08;前后端分离版本&#xff09;环境要求IDEA拉取Gitee源码Mysql 配置Redis 配置后端启动前端配置问题解决 效果展示 若依框架的搭建&#xff08;前后端分离版本&#xff09; 简介 RuoYi-Vue 是一个 Java EE 企业级快速开发平台&#xff0c;基…

线程池(Linux +C)

参考 手写线程池 - C语言版 | 爱编程的大丙 (subingwen.cn) 目录 1.为什么需要线程池&#xff1f; 1&#xff09;线程问题&#xff1a; 2&#xff09;如何解决线程问题&#xff08;线程池的优势&#xff09;&#xff1a; 2.线程池是什么&#xff1f; 1&#xff09;线程的…

无公网IP环境Windows系统使用VNC远程连接Deepin桌面

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;…

Hadoop学习笔记(HDP)-Part.19 安装Kafka

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

使用pandas制作图表

数据可视化对于数据分析的重要性不言而喻&#xff0c;一个优秀的图表有足以一眼就看出关键所在。pandas利用matplotlib实现绘图。能够提供各种各样的图表功能&#xff0c;包括: 单折线图多折线图柱状图叠加柱状图水平叠加柱状图直方图拆分直方图箱型图区域块图形散点图饼图多子…

Linux AMH服务器管理面板本地安装与远程访问

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. Linux 安装AMH 面板2. 本地访问AMH 面板3. Linux安装…

MySQL老是卸载不干净,不会删除注册表,安装总是报错

给大家推荐一款非常使用的工具 geek点击官网下载。 安装完成主页就长这样&#xff1a; 右键点击你要删除的MySQL卸载即可。自动帮你清空注册表等信息。 谁用谁知道&#xff01;&#xff01;&#xff01; 用了感觉不错的话记得回来给我点赞加评论哦&#xff01;&#xff01;&…

科普小知识-3D 打印是什么?

3D 打印是什么&#xff1f;作为近年来备受关注的前沿科技&#xff0c;3D 打印技术正在不断改变着制造业、医疗领域、艺术设计等多个领域的面貌。其又被称为增材制造&#xff0c;是一种通过电脑设计&#xff0c;逐层堆叠材料来创建三维物体的技术。 3D 打印的基本原理 3D 打印…

数据库的索引

索引的特点 1&#xff09;加快查询的速度 2&#xff09;索引自身是一种数据结构&#xff0c;也要占用存储空间 3&#xff09;当我们需要进行增删改的时候&#xff0c;也要对索引进行更新&#xff08;也需要额外的空间开销&#xff09; sql操作 查看索引 show index from …

安装postgresql驱动及python使用pyodbc指定postgresql驱动调用postgresql

注&#xff1a;Python解释器版本(32位/64位)和postgresql驱动版本(32位/64位)需一致。 一、安装postgresql驱动 https://www.postgresql.org/ftp/odbc/versions/msi/ &#xff08;1&#xff09;32位&#xff1a; &#xff08;2&#xff09;64位&#xff1a; 双击安装。全程默…

如何让软文更具画面感,媒介盒子分享

写软文这种带有销售性质的文案时&#xff0c;总说要有画面感&#xff0c;要有想象空间。只有针对目标用户的感受的设计&#xff0c;要了解用户想的是什么&#xff0c;要用可视化的描述来影响用户的感受&#xff0c;今天媒介盒子就和大家分享&#xff1a;如何让软文更具画面感。…

axios调接口传参特殊字符丢失的问题(encodeURI 和 encodeURIComponent)

1、axios调接口特殊字符丢失的问题 项目开发过程中遇到一个接口传参&#xff0c;参数带特殊字符&#xff0c;axios调接口特殊字符丢失的问题 例如接口&#xff1a; get/user/detail/{name} name是个参数直接调接口的时候拼到接口上&#xff0c;get/user/detail/test123#$%&am…

IntelliJ IDEA图形安装教程

IntelliJ IDEA图形安装教程 之前开始Java程序&#xff0c;一直用的eclipse&#xff0c;觉得还可以。一直听说IntelliJ IDEA比eclipse好用很多&#xff0c;但因为比较懒&#xff0c;也没有学习使用。机缘巧合下&#xff0c;尝试用了下&#xff0c;顿时有种相见恨晚的感觉&#…

如何核销百川云网站监测兑换码

注册/登录百川云平台 在电脑端输入百川云网址“https://rivers.chaitin.cn/“&#xff0c;会出现以下界面&#xff1a; 2.点击右上角“立即注册”&#xff0c;使用微信扫一扫注册&#xff0c;注册成功之后&#xff0c;系统会自动跳转到百川云工作台。 免费开通“百川云网站监测…

什么是HTTPS加密协议? ️

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

FreeRTOS系统延时函数分析

一、概述 FreeRTOS提供了两个系统延时函数&#xff0c;相对延时函数vTaskDelay()和绝对延时函数vTaskDelayUntil()。相对延时是指每次延时都是从任务执行函数vTaskDelay()开始&#xff0c;延时指定的时间结束&#xff0c;绝对延时是指每隔指定的时间&#xff0c;执行一次调用vT…

水声功率放大器是什么(驱动水声换能器的原理是什么)

水声功率放大器是一种用于增大水声信号的电子设备。它是水声系统中的关键部件&#xff0c;通常用于驱动水声换能器&#xff0c;将低功率的电信号转换为高功率的水声信号。 水声功率放大器是声呐发射机的重要组成部分&#xff0c;用来提高输出功率,驱动换能器向水中辐射足够能量…

项目管理:为什么项目计划必不可少

项目管理计划定义了如何执行、监督和控制项目。项目计划让我们准确地知道在项目的每个阶段应该做什么&#xff0c;在哪里分配资源和时间&#xff0c;以及在事情超出计划或超出预算时要注意什么。 为了项目中获得成功&#xff0c;管理者需要在前期创建一个项目计划&#xff0c…

高效率:使用DBeaver连接spark-sql

提高运行效率一般采取底层使用spark引擎替换成hive引擎的方式提高效率&#xff0c;但替换引擎配置较为复杂考虑到兼容版本且容易出错&#xff0c;所以本篇将介绍使用DBeaver直接连接spark-sql快速操作hive数据库。 在spark目录下运行以下命令&#xff0c;创建一个SparkThirdSe…

免费通配符和免费多域名证书

免费通配符证书&#xff0c;其特点在于能够为一个主域名及其所有子域名提供加密保护。通常&#xff0c;通配符证书的主域名会以通配符&#xff08;*&#xff09;表示&#xff0c;比如*.example.com&#xff0c;这样就覆盖了blog.example.com、api.example.com等所有子域名。 免…