【JavaEE初阶 — 多线程】wait() notify()

    c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif

    1. 协调多个线程之间的执行先后顺序的方法介绍    


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

    拓展: wait() 和 sleep() 的区别     


wait() 和 sleep()都是用于暂停线程的操作,但它们有明显的区别(先说面试官最关心的):


    (1)使用要求不同    


  • wait() 必须在同步块或同步方法内调用(嵌套一层 synchronized ),否则会抛出IllegalMonitorStateException。
  • 这是因为 wait() 依赖于对象锁来管理线程的等待和唤醒机制。
  • 调用后,当前线程会释放它持有的对象锁,并进入等待状态。

  • sleep()方法可以在任何上下文中调用,不需要获取对象锁
  • 调用后,线程会进入休眠状态,但不会释放它持有的任何锁
  • 所以如果 wait() 和 sleep() 都嵌套一层锁,分别被唤醒时,wait() 会释放锁,而 sleep() 不会释放锁;

    (2)方法所属类不同    


  • wait()   :属于 Object 类的非静态方法。
  • sleep() :属于 Thread 类的静态方法。

    (3)恢复方式不同    


  • wait() 需要被其他线程通过 notify() 或 notifyAll() 显式唤醒;
  • 或者被 wait(long timeout) 的超时参数唤醒。

  • sleep() 在指定时间后自动恢复运行,或通过 interrupt() 提前唤醒,抛出 InterruptedException 异常

    (4)用途不同    


  • wait() 通常用于线程间通信,配合 notify() 或 notifyAll() 来实现线程的协调工作。

  • sleep() 用于让线程暂停执行一段时间,通常用于控制线程的执行频率或模拟延时

     (5)常见错误     


     误用sleep() :    

有时开发者会错误地使用 sleep() 进行线程间通信,但是 sleep() 不释放锁,可能会导致其他线程无法进入同步块,造成线程饥饿或死锁。


     忽略中断:    

sleep() 可能抛出 InterruptedException , 如果不正确处理中断信号,可能会导致线程提前退出或错误行为。


   2. wait()     


   2.1  线程饿死    

如上图,鸟妈妈(CPU)抓虫(调度资源)喂小鸟(线程),就是一个典型的 “线程饿死” 情景:

  • 对于线程饿死,并不是鸟妈妈把捉到的虫全都喂一只鸟宝宝,其他鸟宝宝一点都吃不到,而是鸟妈妈把捉到的虫子,绝大多数都喂给了一个鸟宝宝,剩下的鸟宝宝只能吃到一点点;
  • 使用 wait(),notify() 就是为了优化 “鸟妈妈把大多数的虫子,都分给一只鸟宝宝” 这一行为。

    2.2  调用 wait()     


  • wait() 和 notify 都是Object 的方法;Java 中的任意对象都提供了 wait() 和 notify();
  • wait() 能使当前执行代码的线程进行等待(把线程放到等待队列中);
  • wait() 一被调用,就会释放当前的锁;
  • 满足一定条件时被唤醒,重新尝试获取这个锁;
  • 注意:在判断是否满足唤醒条件时,我们可以把 if(判断条件)  改成 while(判断条件) ,这样可以避免被 interrupt() 类似的方法非法打断。在该文章模拟阻塞队列的 put() 和 take() 有详细解释 


  • wait 要搭配 synchronized 来使用;脱离synchronized,使用 wait 会直接抛出上述异常;
  • 上述异常被抛出的本质,是针对未加锁的锁对象进行释放锁操作


    2.3 唤醒 wait()    

  • 其他线程调用该对象的 notify() ;
  • wait() 等待时间超时(wait() 提供一个带有 timeout 参数的版本,来指定等待时间);
  • 其他线程调用该等待线程的 interrupted(),导致 wait() 抛出  InterruptedException 异常;

  • 在synchronized的代码块中,等到wait() 结束,wait后面到 }的部分,还有一些其他的逻辑;
  • 这些逻辑还是期望在锁的范围内进行调度,所以 wait 后面到}的部分也要嵌套在锁内,wait() 被唤醒后,会重新对这些逻辑上锁,以保证线程安全。

   3. notify()   


    3.1 wait() 和 notify() 的需要同一个对象调用    


  • 通过相同的对象调用 wait() 和 notify() ,是两个线程沟通的桥梁;
  • wait() 和 notify() 针对同一个对象才能生效;如果是不同对象,则没有任何相互的影响和作用~
  • 为了验证这一点,外面写出如下代码:

    代码逻辑:   

  • 在 t1 线程执行到第一个打印日志之后,执行 wait() ,此时就需要通过 t2 线程来唤醒 t1;
  • 我们先用 Scaner 来阻塞 t2,这样操作就可以手动控制 t2 对 t1 的唤醒;
  • 在输入内容后,t2执行 notify(),如果调用 wait() 和 notify() 的两个对象相同,则 t1 会成功被唤醒。

上图是不同对象调用 wait() 和 notify() 的情况,我们再来看看相同对象调用的结果:


    3.2  notify() 要同步方法或同步块中调用    


和 wait() 一样,也是要在同步方法或同步块(嵌套一层 synchronized)中调用;


    3.3 notify() 随机唤醒多个 wait() 中的其中一个    


  • notify()通知正在 wait(), 等待同一个对象锁的其它线程,对其它线程中的一个线程,发出通知notify,并使这个线程重新获取该对象的对象锁 
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈wait状态的线程。(并没有"先来后到")

  • 我们来看程序运行结果,在输入任意内容后,打印的结束日志是不一样的

  • 这就证明了线程调度器随机挑选出一个呈 wait 状态的线程
  • 有两个 wait(),一个 notify(),一定有一个线程没有被唤醒,导致整个进行无法结束;
  • 既然有两个 wait(),我们就设置两个 notify() 即可解决该问题;


  • 在 notify()方法后,当前线程不会马上释放该对象锁;
  • 要等到执行 notify() 所在同步代码块退出之后,才会释放对象锁 ;

   4. notifyAll()   


  •  对于刚刚上面写的代码,只有一个 notify(),就只能唤醒一个 wait():

  • 因此,我们可以考虑用 notifyAll(),唤醒所有相同对象调用 wait() 的线程:

  • 虽然同时唤醒了 t1 和 t2,但是由于 wait() 被唤醒之后,要重新加锁;因此其中某个线程,先加上锁,开始执行,而另一个线程因为加锁失败,再次阻塞等待;
  • 等到先加锁的线程解锁了,后加锁的线程才能加上锁,而继续执行~

    总结    

  • 因为这个原因,notifyAll() 在实际开发中,虽然可以唤醒所有 wait() ,但是用的并不多。
  • 因为不是一口气全部唤醒 wait(),而是每次唤醒其中一个线程,通过多次唤醒,把所有 wait()状态的线程唤醒;
  • notifyAll() 在唤醒其中一个 wait() 状态的线程时,其他线程依旧因为 wait() 尝试重新获取锁对象,而陷入阻塞等待;
  • 比起唤醒所有,我们更希望通过一个一个的 notify() 精确唤醒每一个线程。

     5. 应用 wait() 和 notify() 解决编程题    


    5.1 题目    


     5.2 程序运行结果     


    5.3 完整代码     

package Thread;

public class Demo29 {
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Object locker3 = new Object();
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (locker1) {
                        locker1.wait();
                    }
                    System.out.print("A");
                    synchronized (locker2){
                        locker2.notify();
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (locker2) {
                        locker2.wait();
                    }
                    System.out.print("B");
                    synchronized (locker3){
                        locker3.notify();
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread t3 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (locker3) {
                        locker3.wait();
                    }
                    System.out.println("C");
                    synchronized (locker1){
                        locker1.notify();
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t1.start();
        t2.start();
        t3.start();
        
        //轮子已经造好了,现在需要推一把,让轮子转起来
        //需要确保上述主线程都执行到 wait(),再推轮子
        Thread.sleep(1000);
        synchronized (locker1){
            locker1.notify();
        }
    }
}

    c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

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

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

相关文章

TypeORM在Node.js中的高级应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 TypeORM在Node.js中的高级应用 TypeORM在Node.js中的高级应用 TypeORM在Node.js中的高级应用 引言 TypeORM 基本概念 1. 实体&am…

[Mysql] Mysql的多表查询----多表关系(下)

4、操作 方式二&#xff1a;创建表之后设置外键约束 外键约束也可以在修改表时添加&#xff0c;但是添加外键约束的前提是&#xff1a;从表中外键列中的数据必须与主表中主键列中的数据一致或者是没有数据。 语法&#xff1a; alter table <从表名> add constr…

Ethernet 系列(9)-- 基础学习::ICMP

目录 1. 缩写词&#xff1a; 2. ICMP的目的&#xff1a; 2.1 什么是ICMP&#xff1a; 2.2 什么时候使用ICMP&#xff1a; 3. ICMP 头部&#xff1a; 4. ICMP 报文类型&#xff1a; 4.1 目标不可达&#xff1a; 4.2 重定向&#xff1a; 4.3 超时&#xff1a; 4.4 Ping…

【计算机视觉】FusionGAN

1. FusionGAN论文阅读 abreheret/FusionGAN: Pytorch implementation of "Generating a Fusion Image: One’s Identity and Another’s Shape" 1.1. WHY 在现实世界中,将对象或人物转换为期望的形状是一种常用技术,但现有的图像翻译方法在处理身份和形状时存在…

<项目代码>YOLOv8 瞳孔识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

24/11/12 算法笔记<强化学习> 自注意力机制

自注意力机制&#xff08;Self-Attention Mechanism&#xff09;&#xff0c;也称为内部注意力机制&#xff0c;是一种在深度学习模型中&#xff0c;特别是在自然语言处理&#xff08;NLP&#xff09;和计算机视觉领域中广泛使用的机制。它允许模型在处理序列数据时&#xff0c…

前后端交互之动态列

一. 情景 在做项目时&#xff0c;有时候后会遇到后端使用了聚合函数&#xff0c;导致生成的对象的属性数量或数量不固定&#xff0c;因此无法建立一个与之对应的对象来向前端传递数据&#xff0c;这时可以采用NameDataListVO向前端传递数据。 Data Builder AllArgsConstructo…

k8s服务内容滚动升级以及常用命令介绍

查看K8S集群所有的节点信息 kubectl get nodes 删除K8S集群中某个特定节点 kubectl delete nodes/10.0.0.123 获取K8S集群命名空间 kubectl get namespace 获取K8S所有命名空间的那些部署 kubectl get deployment --all-namespaces 创建命名空间 web界面上看到的效果,但是…

【视觉SLAM】1-概述

读书笔记 文章目录 1. 经典视觉SLAM框架2. 数学表述2.1 运动方程2.2 观测方程2.3 问题抽象 1. 经典视觉SLAM框架 传感器信息读取&#xff1a;相机图像、IMU等多源数据&#xff1b;前端视觉里程计&#xff08;Visual Odometry&#xff0c;VO&#xff09;&#xff1a;估计相机的相…

低成本出租屋5G CPE解决方案:ZX7981PG/ZX7981PM WIFI6千兆高速网络

刚搬进新租的房子&#xff0c;没有网络&#xff0c;开个热点&#xff1f;续航不太行。随身WIFI&#xff1f;大多是百兆级网络。找人拉宽带&#xff1f;太麻烦&#xff0c;退租的时候也不能带着走。5G CPE倒是个不错的选择&#xff0c;插入SIM卡就能直接连接5G网络&#xff0c;千…

如何在Typora中绘制流程图

如何在Typora中绘制流程图 在撰写文档时&#xff0c;清晰的流程图能极大地提升信息传递的效率。Typora是一款优秀的Markdown编辑器&#xff0c;支持通过Mermaid语法快速绘制流程图。本文将介绍如何在Typora中创建和自定义流程图&#xff0c;帮助你用更直观的方式呈现逻辑结构和…

莱特币转型MEME币:背后隐含的加密市场现象

随着加密市场的风云变幻&#xff0c;莱特币&#xff08;LTC&#xff09;这款曾经的“老牌矿币”近日以自嘲式推文宣布“自己是一个MEME币”&#xff0c;迅速引发了市场的广泛关注和一波围绕MEME币的炒作浪潮。这一举动看似玩笑&#xff0c;却反映出当前加密市场的一种微妙转变&…

【代码大模型】Is Your Code Generated by ChatGPT Really Correct?论文阅读

Is Your Code Generated by ChatGPT Really Correct? Rigorous Evaluation of Large Language Models for Code Generation key word: evaluation framework, LLM-synthesized code, benchmark 论文&#xff1a;https://arxiv.org/pdf/2305.01210.pdf 代码&#xff1a;https:…

LC12:双指针

文章目录 125. 验证回文串 本专栏记录以后刷题碰到的有关双指针的题目。 125. 验证回文串 题目链接&#xff1a;125. 验证回文串 这是一个简单题目&#xff0c;但条件判断自己写的时候写的过于繁杂。后面参考别人写的代码&#xff0c;首先先将字符串s利用s.toLowerCase()将其…

MySQL5.7.37安装配置

1.下载MySQL软件包并解压 2.配置环境变量 3.新建my.ini文件并输入信息 [mysqld] #端口号 port 3306 #mysql-5.7.27-winx64的路径 basedirC:\mysql-5.7.37\mysql-5.7.37-winx64 #mysql-5.7.27-winx64的路径\data datadirC:\mysql-5.7.37\mysql-5.7.37-winx64\data #最大连接数…

python习题4

1 判断车牌归属地 输入一串车牌号&#xff0c;按e结束&#xff0c;判断车牌归属于那里 例如&#xff1a; 输入&#xff1a; jingA12345 huB34567 zheA99999 e 输出&#xff1a; jing hu zhe chepai input(请输入车牌号&#xff1a;\n) lst [] while chepai ! e:lst…

【原创】java+ssm+mysql社区疫情防控管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

《深度学习》VGG网络

文章目录 1.VGG的网络架构2.案例&#xff1a;手写数字识别 学习目标&#xff1a; 知道VGG网络结构的特点能够利用VGG网络完成图像分类 2014年&#xff0c;⽜津⼤学计算机视觉组&#xff08;Visual Geometry Group&#xff09;和GoogleDeepMind公司的研究员⼀起研发出了新的深度…

探索 Python HTTP 的瑞士军刀:Requests 库

文章目录 探索 Python HTTP 的瑞士军刀&#xff1a;Requests 库第一部分&#xff1a;背景介绍第二部分&#xff1a;Requests 库是什么&#xff1f;第三部分&#xff1a;如何安装 Requests 库&#xff1f;第四部分&#xff1a;Requests 库的基本函数使用方法第五部分&#xff1a…

无桥Boost-PFC 双闭环控制MATLAB仿真

一、无桥Boost-PFC原理概述 无桥 Boost-PFC&#xff08;Power Factor Correction&#xff0c;功率因数校正&#xff09;的工作原理是通过特定的电路结构和控制策略&#xff0c;对输入电流进行校正&#xff0c;使其与输入电压同相位&#xff0c;从而提高电路的功率因数&#xf…