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

news2024/11/20 12:34:01

目录

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

二、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

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

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

相关文章

2023年4K投影仪怎么选?极米H6 4K高亮版怎么样?

随着人们生活水平的不断提升&#xff0c;投影仪也逐渐成为了家家户户的必备家居好物。近十年来&#xff0c;中国投影仪市场规模增长数倍&#xff0c;年均增长率大幅提高。从近10年的发展趋势来看&#xff0c;投影仪行业处于高速发展期。 此前&#xff0c;极米科技推出的极米H6…

crmeb本地开发配置代理

crmeb 是一个开源的商城系统&#xff0c; v5 版本是一个前后端分离的项目&#xff0c; 我们从git仓库中下载下来的是一个文件夹&#xff0c;其结构是这样的 我的系统没有使用docker &#xff0c;使用的是 laragon 的系统 所以首先我们要在 nginx 中配置 之后&#xff0c; 我们…

IDEA使用git从远程仓库获取项目

将地址填入url中 然后直接clone就行

Ant Design Vue 年选择器

文章目录 参考文档效果展示实现过程 参考文档 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; DatePicker 日期选择框 大佬&#xff1a;搬砖小匠&#xff08;Ant Design vue 只选择年&#xff09; 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案…

C语言——指针(五)

&#x1f4dd;前言&#xff1a; 上篇文章C语言——指针&#xff08;四&#xff09;更加深入的介绍了不同类型指针的特点&#xff0c;这篇文章主要想记录一下函数与指针的结合运用以及const和assert关于指针的用法&#xff1a; 1&#xff0c;函数与指针 2&#xff0c;const 3&am…

十五届蓝桥杯分享会(一)

注&#xff1a;省赛4月&#xff0c;决赛6月 一、蓝桥杯整体介绍 1.十四届蓝桥杯软件电子赛参赛人数&#xff1a;C 8w&#xff0c;java/python 2w&#xff0c;web 4k&#xff0c;单片机 1.8w&#xff0c;嵌入式/EDA5k&#xff0c;物联网 300 1.1设计类参赛人数&#xff1a;平…

STL(一)(pair篇)

1.pair的定义和结构 在c中,pair是一个模板类,用于表示一对值的组合它位于<utility>头文件中 pair的定义如下: template<class T1, class T2> struct pair{T1 first; //第一个值T2 second; //第二个值//构造函数pair();pair(const T1&x,const T2&y);//比较…

域名与SSL证书

域名是互联网上的地址标识符&#xff0c;它通过DNS&#xff08;Domain Name System&#xff09;将易于记忆的人类可读的网址转换为计算机可以理解的IP地址。当用户在浏览器中输入一个网址时&#xff0c;实际上是通过DNS解析到对应的服务器IP地址&#xff0c;从而访问到相应的网…

诚邀莅临,共商发展丨“交汇未来”行业大模型高峰论坛

大会简介 今年以来&#xff0c;以ChatGPT为典型代表的大模型在全球数字科技界引起极大关注&#xff0c;其强大的数据处理能力和泛化性能使得其在各个领域都有广泛的应用前景&#xff0c;驱动千行百业的数字化转型升级&#xff0c;成为新型工业化和实体经济的重要推动力&#x…

【C语言】vfprintf函数

vfprintf 是 C 语言中的一个函数&#xff0c;它是 fprintf 函数的变体&#xff0c;用于格式化输出到文件中。vfprintf 函数接受一个格式化字符串和一个指向可变参数列表的指针&#xff0c;这个列表通常是通过 va_list 类型来传递的。vfprintf 函数的主要用途是在需要处理不定数…

数据分析基础之《matplotlib(5)—直方图》

一、直方图介绍 1、什么是直方图 直方图&#xff0c;形状类似柱状图却有着与柱状图完全不同的含义。直方图牵涉统计学的概念&#xff0c;首先要对数据进行分组&#xff0c;然后统计每个分组内数据元的数量。在坐标系中&#xff0c;横轴标出每个组的端点&#xff0c;纵轴表示频…

PyQt5 - 鼠标连点器

文章目录 ⭐️前言⭐️鼠标连点器 ⭐️前言 本次设计的鼠标连点器主要是对QVBoxLayout、QHBoxLayout和QStackedWidget进行一个回顾复习&#xff0c;加深对它们的理解&#xff0c;提高运用的熟练度。 ⭐️鼠标连点器 如以下代码所示&#xff0c;设计两个QWidget控件&#xff…

【学习笔记】python仅拷贝list的值,引出浅拷贝与深拷贝

一、python 仅拷贝list的值&#xff08;来源于gpt&#xff09; 在 Python 中&#xff0c;可以使用切片或 copy() 方法来仅拷贝列表的值。 1、使用切片 a [1, 2, 3, 4, 5] b a[:] # 通过切片来拷贝 a 的值 在上面的代码中&#xff0c;我们使用切片来拷贝列表 a 的值&#xff…

Web前端 ---- 【Vue】Vuex的使用(辅助函数、模块化开发)

目录 前言 Vuex是什么 Vuex的配置 安装vuex 配置vuex文件 Vuex核心对象 actions mutations getters state Vuex在vue中的使用 辅助函数 Vuex模块化开发 前言 本文介绍一种新的用于组件传值的插件 —— vuex Vuex是什么 Vuex 是一个专为 Vue.js 应用程序开发的状态…

MDIO读写控制实验

简介&#xff1a; 以太网&#xff1a; 以太网(Ethernet)是当今现有局域网采用的最通用的通信协议标准&#xff0c; 该标准定义了在局域网中采用的电缆类型和信号处理方法。以太网的分类有标准以太网&#xff08;10Mbit/s&#xff09;、 快速以太网(100Mbit/s)和千兆以太网&am…

计算机服务器中了mkp勒索病毒怎么办,mkp勒索病毒解密数据恢复

网络技术的不断发展&#xff0c;也为网络安全带来了威胁&#xff0c;近期云天数据恢复中心的工程师陆续接到很多企业的求助&#xff0c;在本月&#xff0c;很多企业的计算机服务器遭到了mkp勒索病毒攻击&#xff0c;导致企业计算机系统瘫痪&#xff0c;无法正常工作&#xff0c…

苹果 macOS 14.1.2 正式发布 更新了哪些内容?

苹果今日向 Mac 电脑用户推送了 macOS 14.1.2 更新&#xff08;内部版本号&#xff1a;23B92 | 23B2091&#xff09;&#xff0c;本次更新距离上次发布隔了 28 天。 需要注意的是&#xff0c;因苹果各区域节点服务器配置缓存问题&#xff0c;可能有些地方探测到升级更新的时间略…

配置Smart Link主备备份示例

1、Smart Link和Monitor Link简介。 Smart Link&#xff0c;又叫做备份链路。一个Smart Link由两个接口组成&#xff0c;其中一个接口作为另一个的备份。Smart Link常用于双上行组网&#xff0c;提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案&#xff…

Navicat 技术指引 | 适用于 GaussDB 分布式的模型功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

104.进程创建

目录 进程创建相关的函数 获取当前进程的进程ID&#xff08;PID&#xff09; 获取当前进程的父进程ID&#xff08;PPID&#xff09; 创建一个新的进程 fork()剖析 调用格式 创建子进程 子进程与父进程 父子进程执行流 代码演示 进程创建相关的函数 Linux中进程ID为pi…