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

news2025/7/27 17:30:55

文章目录

  • 协调多个线程间的执行顺序
    • join 和 wait 区别
    • sleep 和 wait 区别
  • wait()方法
    • 线程饿死
    • 调用 wait()
    • 唤醒 wait()
  • notify()方法
    • wait() 和 notify() 需对同一对象使用
    • 确保先 wait ,后 notify
    • 多个线程在同一对象上wait notify随机唤醒一个wait
  • notifyAll()方法
  • 应用 wait() 和 notify()协调多个线程的执行示例

协调多个线程间的执行顺序

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

在这里插入图片描述

比如:球场上的每个运动员都是独立的"执行流",可以认为是一个"线程"
而完成一个具体的进攻得分动作,则需要多个运动员的相互配合,按照一定的顺序执行一定的动作,线程1 先"传球",线程2 才能"扣篮"

在这里插入图片描述
注意:wait,notify,notifyAll都是Object类的方法,也就是说任意对象的线程都可以进行协调执行顺序的操作

join 和 wait 区别

  • join也是等
    join是等另一个线程执行完,才继续走

  • wait也是等
    wait是等到另一个线程执行notify,才继续走(不需要另一个线程执行完)

wait 和 join 类似的是:都提供了"死等"(不带参数)的版本 和 “超过时间”(带参数)的版本

sleep 和 wait 区别

在这里插入图片描述

  1. 使用要求:
  • wait() 必须搭配锁,先加锁,才能使用 wait,否则会抛出IllegalMonitorSteteException
  • 若在synchronized内部调用后,当前线程会释放锁.并进入等待状态
  • sleep() 方法可以在任意上下文使用,不需要锁
  • 若在synchronized内部调用后,不会释放锁,线程进入休眠状态
  1. 方法所属类不同
  • wait() : 属于Object类的非静态方法
  • sleep() : 属于Thread类的静态方法
  1. 唤醒方式
  • wait()需要被其他线程通过notify()或notifyAll()唤醒 或是wait(long timeout)超时唤醒
  • sleep()在超过时间后自动唤醒 或是 interrupt()提前唤醒,抛出InterruptedException异常
  1. 用途不同
  • wait()通常配合 notify() 和 notifyAll()实现线程间的协调工作
  • sleep() 用于让线程暂停一段时间,比如停下来看一下日志或者我们前面保证会出现死锁的状态也是通过sleep实现的,用来模拟延时等等
  1. 误用sleep,sleep()并不会释放锁,可能会导致其他线程无法进入同步块,造成线程饥饿或死锁
    synchronized(locker){
    Thread.sleep(1000);
    },这样sleep抱着锁睡~~,其他线程也无法获取这个锁
    sleep()若被提前唤醒或抛出InterruptedException异常,若不处理,会导致线程提前退出

wait()方法

wait做的事情:

  • 使当前执行代码的线程进行等待(把线程放到等待队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒,重新尝试获取这个锁

wait要搭配 synchronized来使用,脱离synchronized使用.抛出IllegalMonitorSteteException异常

wait结束等待的条件:

  • 其他线程调用该对象的notify方法
  • wait等待超过时间
  • 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException异常

在这里插入图片描述

线程饿死

在这里插入图片描述
当多个线程竞争一把锁的时候,获取到锁的线程如果释放了,其他是哪个线程拿到锁?—>不确定(随机调度)

操作系统的调度是随机的,其他线程都属于在锁上阻塞等待,是阻塞状态,而当前释放锁的这个线程,是就绪状态,所以很大概率还是这个线程再次拿到锁

这样就是导致其他线程一直捞不到CPU资源去执行,线程就饿死了

调用 wait()

  • wait() 和 notify() 都是Object方法–>Java中的任意对象都提供了 wait() 和 notify()
  • 当拿到锁的线程,发现要执行的任务,时机还不成熟,就是用 wait 进行阻塞等待(把线程放到等待队列中)
  • wait() 一被调用,就会释放当前的锁
    在这里插入图片描述
  • wait的使用要搭配synchronized使用;脱离synchronized,就会抛出上述异常
  • 上述抛出异常的本质:就是对未加锁的对象进行解锁操作

唤醒 wait()

  • 使用其他线程该对象的notify() 唤醒wait线程,wait就会解除,重新获取到锁,继续执行并返回
  • 要求synchronized的锁对象和wait的对象是同一个

notify()方法

notify 方法是唤醒等待的线程

  • notify 方法也要在同步方法或同步代码块中(就是在synchronized修饰的方法或代码块中)调用,该方法是用来通知那些等待该对象的对象锁的其他线程,对其发出通知,使他们重新获取该对象的对象锁
  • 如果多个线程等待,则由线程调度器随机挑选一个呈 wait 状态的线程(没有"先来后到")
  • notify()后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁

wait() 和 notify() 需对同一对象使用

  • 通过相同的对象调用 wait() 和 notify() ,是两个线程沟通的桥梁
  • wait() 和 notify() 针对同一对象才会生效,不同对象使用是没有相互的影响和作用的
  • wait() 操作必须搭配锁来进行,notify操作原则上不涉及加锁解锁操作,但是在Java中强制要求notify搭配synchronized使用

在这里插入图片描述
在这里插入图片描述
在Java中强制要求notify搭配synchronized使用,操作系统原生API中wait必须搭配锁使用,notify则不需要

确保先 wait ,后 notify

  • 务必确保,先wait,后notify,才有作用
  • 如果先notify,后wait,此时wait无法被唤醒,notify的这个线程虽然没有副作用(notify一个没有wait的对象,并不会报错),但是就相当于后wait的这个线程无法被唤醒了,他的notify在他没有wait之前就已经通知过他了,毫无作用

在这里插入图片描述
在这里插入图片描述

多个线程在同一对象上wait notify随机唤醒一个wait

  • 如果有多个线程在同一个对象上wait,进行notify的时候是随机唤醒其中一个线程
  • 一次notify 唤醒一个wait
  • 多个线程在同一对象上等待,线程调度器随机挑选出一个呈wait的线程(并没有"先来后到")
public class Demo25 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() ->{
            try{
                System.out.println("t1 wait 之前");
                synchronized (locker){
                    locker.wait();
                }
                System.out.println("t1 wait 之后");
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        Thread t2 = new Thread(() ->{
            try{
                System.out.println("t2 wait 之前");
                synchronized (locker){
                    locker.wait();
                }
                System.out.println("t2 wait 之后");
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        Thread t3 = new Thread(() ->{
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入任意内容,唤醒一个线程");
            scanner.next();
            synchronized (locker){
                locker.notify();
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
再加一个notify,一次notify 唤醒一个 wait

public class Demo25 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() ->{
            try{
                System.out.println("t1 wait 之前");
                synchronized (locker){
                    locker.wait();
                }
                System.out.println("t1 wait 之后");
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        Thread t2 = new Thread(() ->{
            try{
                System.out.println("t2 wait 之前");
                synchronized (locker){
                    locker.wait();
                }
                System.out.println("t2 wait 之后");
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        Thread t3 = new Thread(() ->{
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入任意内容,唤醒一个线程");
            scanner.next();
            synchronized (locker){
                locker.notify();
            }
            System.out.println("输入任意内容,唤醒另一个线程");
            scanner.next();
            synchronized (locker){
                locker.notify();
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

notifyAll()方法

  • 对于上述多个线程在同一对象上等待,我们可以考虑使用notifyAll.唤醒同一对象的所有wait的线程
    在这里插入图片描述
  • 虽然同时唤醒了,t1 和 t2 由于 wait 唤醒之后,要重新加锁,其中某个线程,先加上锁,开始执行,另一个线程因为加锁失败,再次阻塞等待
  • 等到先走的线程解锁了,后走的线程才能加上锁,继续执行

总结:

  • 因为这个原因,notifyAll()在实际开发中,虽然可以唤醒所有的wait(),但使用并不多
  • notifyAll()在唤醒其中一个wait状态的线程时,其他的线程依旧因为wait()尝试重新获取锁对象,而陷入阻塞等待
  • 而且一般这些wait的线程都是干同样的工作的,唤醒谁,其实都一样~~

应用 wait() 和 notify()协调多个线程的执行示例

题目:

有三个线程,分别只能打印A ,B和C
要求按顺序打印ABC,打印10次
输出示例:
ABC
ABC
ABC…

public class Demo26 {
    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();
            }
        });
        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();
            }
        });
        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();
            }
        });
        t1.start();
        t2.start();
        t3.start();

        // 主线程中, 先通知一次 locker1, 让上述逻辑从 t1 开始执行
        // 需要确保上述三个线程都执行到 wait, 再进行 notify
        Thread.sleep(1000);//wait 确保三个线程都wait了,再执行notify
        synchronized (locker1) {
            locker1.notify();
        }
    }
}

在这里插入图片描述

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

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

相关文章

安全-JAVA开发-第二天

Web资源访问的流程 由此可见 客户访问JAVA开发的应用时 会先通过 监听器&#xff08;Listener&#xff09;和 过滤器&#xff08;Filter&#xff09; 今天简单的了解下这两个模块的开发过程 监听器&#xff08;Listener&#xff09; 主要是监听 我们触发了什么行为 并进行反应…

Python基础:文件简单操作

&#x1f343;引言 手把手带你快速上手Python Python基础专栏 一、&#x1f343;文件是什么 变量是把数据保存到内存中. 如果程序重启/主机重启, 内存中的数据就会丢失。 要想能让数据被持久化存储, 就可以把数据存储到硬盘中. 也就是在文件中保存。 通过文件的后缀名, 可以看…

深度学习项目之RT-DETR训练自己数据集

RT-DETR 1.模型介绍&#x1f4cc; 什么是 RT-DETR &#xff1f;&#x1f4d6; 核心改进点&#x1f4ca; 结构示意&#x1f3af; RT-DETR 优势⚠️ RT-DETR 缺点&#x1f4c8; 应用场景&#x1f4d1; 论文 & 官方仓库2.模型框架3.Yaml配置文件4.训练脚本5.训练完成截图6.总结…

以太网帧结构和封装【二】-- IP头部信息

1字节 byte 8比特 bit 【位和比特是同一个概念】 比特/位&#xff0c;字节之间的关系是&#xff1a; 位&#xff08;Bit&#xff09; 中文名&#xff1a;位&#xff08;二进制位&#xff09;。 英文名&#xff1a;Bit&#xff08;Binary Digit 的缩写&#xff09;。 含义&…

Promtail采集服务器本地日志存储到Loki

✅ 一、前提条件 已安装 Loki 服务 日志文件目录可访问&#xff08;如 /var/log&#xff09; 具备 sudo 权限 &#x1f9e9; 二、下载 Promtail 二进制文件 # 替换为你想要的版本 VERSION"3.5.1"# 创建目录 sudo mkdir -p /opt/promtail cd /opt/promtail# 下载并…

学习STC51单片机27(芯片为STC89C52RCRC)

每日一言 你读过的书、走过的路、流过的汗&#xff0c;终将成就独一无二的你。 硬件&#xff1a;LCD1602液晶显示 非标协议外设 概述 LCD1602&#xff08;Liquid Crystal Display&#xff09;是一种工业字符型液晶&#xff0c;能够同时显示 1602 即 32 字符(16列两行) 那我…

DA14531_beacon_大小信标设备开发

蓝牙信标是一款通过广播指定蓝牙信号&#xff0c;实现信标信号扫描、识别和获得辅助信息的电子产品。 不同品名的蓝牙信标采用相同的 UUID 和广播信号格式&#xff0c;但在 MAC 地址、工作寿命、体积和广播周期上有所差异。 小武编程巧用DA14531开发一款蓝牙信标.

【算法训练营Day06】哈希表part2

文章目录 四数相加赎金信三数之和四数之和 四数相加 题目链接&#xff1a;454. 四数相加 II 这个题注意它只需要给出次数&#xff0c;而不是元组。所以我们可以分治。将前两个数组的加和情况使用map存储起来&#xff0c;再将后两个数组的加和情况使用map存储起来&#xff0c;ke…

Word双栏英文论文排版攻略

word写双栏英文论文的注意事项 排版首先改字体添加连字符还没完呢有时候设置了两端对齐会出现这样的情况&#xff1a; 公式文献 等我下学期有时间了&#xff0c;一定要学习Latex啊&#xff0c;word写英文论文&#xff0c;不论是排版还是公式都很麻烦的&#xff0c;而Latex一键就…

乡村三维建模 | 江苏农田无人机建模案例

测绘是农田建设的基础工作&#xff0c;测绘的质量和效率直接影响农田建设的进度和成果。传统的人工测量、地面测量等测绘手段&#xff0c;存在效率低、精度差、受环境影响大、成本高等缺点&#xff0c;难以满足高标准农田建设的要求。而无人机倾斜摄影技术具有高效、精确、灵活…

2025 5 月 学习笔记

计算高斯半径&#xff0c;用于生成高斯热图 这个的意义是什么 有什么作用&#xff1f; 14 核心意义&#xff1a;平衡定位精度与检测鲁棒性 在基于热图的目标检测方法&#xff08;如CenterNet、CornerNet等&#xff09;中&#xff0c;计算高斯半径的核心意义在于​​在精确…

SpringBoot(七) --- Redis基础

目录 前言 一、Redis入门 二、Redis常用数据类型 三、Redis常用命令 1. 字符串操作命令 2. 哈希操作命令 3. 列表操作命令 4. 集合操作命令 5. 有序集合操作命令 6.通用命令 四、在Java中操作Redis 前言 Redis是一个基于内存的key-value结构数据库&#xff0c;有以下…

从OSI到TCP/IP:网络协议的演变与作用

个人主页&#xff1a;chian-ocean 文章专栏-NET 从OSI到TCP/IP&#xff1a;网络协议的演变与作用 个人主页&#xff1a;chian-ocean文章专栏-NET 前言网络发展LANWAN 协议举个例子&#xff1a; 协议的产生背景 协议的标准化OSI模型参考OSI各个分层的作用各层次的功能简介 TCP/…

Stream流性能分析及优雅使用

文章目录 摘要一、Stream原理解析1.1、Stream总概1.2、Stream运行机制1.2.1、创建结点1.2.1、搭建流水线1.2.3、启动流水线 1.3、ParallelStream 二、性能对比三、优雅使用3.1 Collectors.toMap()3.2 findFirst()&#xff0c;findAny()3.3 增删元素3.4 ParallelStream 四、总结…

【和春笋一起学C++】(十七)C++函数新特性——内联函数和引用变量

C提供了新的函数特性&#xff0c;使之有别于C语言。主要包括&#xff1a; 内联函数&#xff1b;按引用传递变量&#xff1b;默认参数值&#xff1b;函数重载&#xff08;多态&#xff09;&#xff1b;模版函数&#xff1b; 因篇幅限制&#xff0c;本文首先介绍内联函数和引用…

proteus新建工程

1 点击新建工程 2 输入项目名&#xff0c;选择工程文件夹 3 下一步 4 不创建pcb 5 直接下一步 6 点击完成 7 创建完毕

RTC实时时钟DS1338Z-33/PT7C433833WEX国产替代FRTC1338S

FRTC1338S是NYFEA徕飞公司推出的一种高性能的实时时钟芯片&#xff0c;它采用了SOP8封装技术&#xff0c;这种技术因其紧凑的尺寸和出色的性能而被广泛应用于各类电子设备中。 FRTC1338S串行实时时钟(RTC)是一种低功耗的全二进制编码十进制(BCD)时钟/日历外加56字节的非易失性…

Redis命令使用

Redis是以键值对进行数据存储的&#xff0c;添加数据和查找数据最常用的2个指令就是set和get。 set&#xff1a;set指令用来添加数据。把key和value存储进去。get&#xff1a;get指令用来查找相应的键所对应的值。根据key来取value。 首先&#xff0c;我们先进入到redis客户端…

【免费数据】1980-2022年中国2384个站点的水质数据

水&#xff0c;是生命之源&#xff0c;关乎着地球上每一个生物的生存与发展。健康的水生生态系统维持着整个水生态的平衡与活力&#xff1b;更是确保人类能持续获得清洁水源的重要保障。水质数据在水质研究、海洋生物量测算以及生物多样性评估等诸多关键领域都扮演着举足轻重的…

Git 极简使用指南

Git 是一个强大的分布式版本控制系统&#xff0c;但入门只需要掌握几个核心概念和命令。本指南旨在帮助你快速上手&#xff0c;处理日常开发中最常见的 80% 的场景。 核心概念 仓库 (Repository / Repo): 你的项目文件夹&#xff0c;包含了项目的所有文件和完整的历史记录。…