多线程(初阶五:wait和notify)

news2024/11/21 0:22:36

目录

一、概念

二、用法

(1)举个栗子:

(2)wait和notify的使用

1、没有上锁的wait

2、当一个线程被wait,但没有其他线程notify来释放这个wait

3、两个线程,有一个线程wait,有一个线程notify来释放wait

4、notifyAll

(3)wait的三个选项

三、wait、sleep、join


一、概念

我们知道,多线程在系统中的调度是随机的,我们不能干预多个线程的执行顺序,但是我们可以使某个线程放弃被系统调用,让其他线程先被调用,这样,可以达到我们的预期效果;

wait就是让多线程进行锁竞争的时候,让后执行的线程,放弃和别的线程进行锁竞争,别的线程执行完后,别的线程使用notify,将wait的线程不想进行锁竞争这个信息释放掉,再次和其他线程锁竞争。等待,通知的机制(和join用途类似)


二、用法

(1)举个栗子:

现在有很多滑稽老铁要去ATM里,滑稽A是取钱的,滑稽B是存钱的,滑稽C是运钞票的人员,负责给ATM机补充钱,防止ATM机没钱了,别人取不到钱。而这里的滑稽A,滑稽B,滑稽C我们当做是线程,每次去ATM机里,只能有一个人进去,相当于上锁了,其他人不能进去,等ATM机里面的人完成操作后,出来后,别的人才能进去,但是这里是多线程的原因,其他线程会有锁竞争。     

当A进去ATM机里后,就上锁,其他人不能进去,如图:

把滑稽A比作是线程,当A线程进去后就会上锁,A线程要进行取钱的操作,其他线程不能进去操作,当A线程执行完自己的操作后,其他线程才能去锁竞争。

但是如果ATM机里面没有钱时,A线程就不能完成取钱这个操作,它会退出ATM机,但退出后呢,它因为没有完成取钱的操作,就会想继续进去ATM里面,完成取钱这个操作,就会继续和其他线程进行锁竞争,因为A线程拿到了锁,处于RUNNABLE状态,其他线程因为阻塞,处于BLOCKED状态,需要被系统唤醒后,才能去竞争锁,但是线程A呢,不用唤醒就能去竞争锁,后面又被A线程拿到锁的可能性还是很大的(类似近水楼台先得月)。如果这样子,那线程A频繁的进去又出来,干不了事,但是其他线程也不能进去操作,用通俗的话说,就是占着茅坑不拉屎的意思。也就出现线程安全问题了。其他线程,无法拿到锁,这个情况称为 “线程饿死”。

这里的线程A的代码大概逻辑是这样的:

当A线程没有取到钱,就会一直重复加锁,解锁的操作。

这样的bug没有死锁那么严重,但也是要解决的。那如何解决呢。这时,就可以用wait和notify了。期望改进成,如下图:

这里的wait内部做了三件事

(1)释放锁,给其他线程竞争锁

(2)进入阻塞等待

(3)等其他线程使用notify后,解除wait,参与到锁竞争中

(2)wait和notify的使用

wait的使用前提必须是当前对象被上锁了才能使用,不能你对象没被上锁,就wait了,那也不知道是在wait谁。同时,有线程wait了,也必须有其他线程notify来释放这个wait,不然这个wait就会一直阻塞。

1、没有上锁的wait

代码:

public class TestDemo1 {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        locker.wait();
    }
}

执行结果:

2、当一个线程被wait,但没有其他线程notify来释放这个wait

代码:

public class TestDemo3 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("wait之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait之后");
            }
        });
        t1.start();
    }
}

执行结果:

打印不了 “wait之后”,一直是阻塞等待状态,在jconsole中,状态如图

3、两个线程,有一个线程wait,有一个线程notify来释放wait

代码:

public class TestDemo2 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t1 wait之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t2 wait之后");
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (locker) {
                System.out.println("t2 notify之前");
                locker.notify();
                System.out.println("t2 notify之后");
            }
        });
        t1.start();
        t2.start();
    }
}

注意:这里的释放wait的notify,用的对象必须是要一样的,如果不一样,wait是不能被释放的,t1也就不能被唤醒了.

在系统中,notify可以不用上锁,但是在java中,规定了要上锁,而且上锁的对象也要和notify对象一样,所以和系统是有区别的。

执行结果:

结果解析:

t1 和 t2 执行的时候

(1)因为t1 sleep了1秒,所以能保证t1 先wait,所以先打印 “t1 wait之前”,这时,t1就进入阻塞等待状态。

(2)t2线程sleep了1秒后,获得这个locker锁,打印“t2 notify 之前”,当t2线程执行了notify后,t1 线程的wait就被释放了。

(3)因为t2还在持有锁,所以t1会还会进入阻塞,t2打印 “t2 notify之后” ,释放锁。

(4)t1拿到锁,再打印“t1 wait之后”。

4、notifyAll

唤醒等待这个对象的所有线程;假设有很多个线程,都使用同一个对象wait,这时,使用notifyAll,所有使用了这个对象的wait的线程,都会被唤醒。

注意:当这些线程都被唤醒时,就要重新获取锁,他们还是要进行锁竞争的,这里也就相当于串行执行了(线程调度还是随机调度的)。而且使用notifyAll后,全部使用同一对象wait的线程,都被唤醒了,不好控制,更加推荐使用notify。

(3)wait的三个选项

如图:

没有参数的就是死等,但是很多情况,死等是不合理的,所以我们加参数,就是让某个线程在一定时间wait,如果超出了这个时间,就不wait了,直接过掉wait。

有一个参数的精确范围是毫秒级别,两个参数的精确范围是纳秒级别。


三、wait、sleep、join

wait:需要搭配synchronized使用,线程wait时,处于WAITING状态,需要其他线程notify后,才能被唤醒,或者设置时间,到时就唤醒,可以兜底。

sleep:线程sleep时,要到一定休眠时间才能被唤醒,但是也能被interrupt终止,但是这种情况是会抛异常的,是非常规手段,不符合我们预期的效果。

join:啥线程调用join,当前线程就要等啥线程执行完,才能之前当前线程;和wait一样有参数可以选择,到时就不等了。

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

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

相关文章

React项目使用NProgress作为加载进度条

React项目使用NProgress作为加载进度条 0、效果1、react安装依赖2、使用3.进度条颜色设置 文档参考:https://zhuanlan.zhihu.com/p/616245086?utm_id0 0、效果 如下,可全局在页面顶部有一条进度条 1、react安装依赖 yarn add nprogress通过以上安装…

vue之mixin混入

vue之mixin混入 mixin是什么? 官方的解释: 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的…

二次元检测设备导轨修复指南

二次元检测设备是一种高精度的测量仪器,用于检测物体表面的形状、尺寸和精度等。直线导轨是二次元检测设备中最重要的组成部分之一,它的精度和稳定性直接影响到设备的测量结果和可靠性,因此,对导轨进行修复和保养是非常重要的。 直…

网络类型解析(基础):探索通信世界的多样面貌

在当今数字化时代,网络已经成为人们生活和工作中不可或缺的一部分。从个人设备之间的直接通信到全球范围的数据传输,不同类型的网络为我们提供了多种连接方式和通信选择。透过对这些网络类型的解析,我们将更好地理解它们的特点、优势和适用场…

九章量子计算机:探索量子世界的革命性工具

九章量子计算机:探索量子世界的革命性工具 一、引言 九章量子计算机的推出,是近年来科技界最为引人瞩目的成就之一。这款基于量子力学的计算机,以其独特的计算方式和潜在的应用前景,引发了全球范围内的关注和讨论。本文将深入探讨九章量子计算机的原理、技术特点、应用前景…

nodejs_vue+vscode美容理发店会员管理系统un1dm

按照设计开发一个系统的常用流程来描述系统,可以把系统分成分析阶段,设计阶段,实现阶段,测试阶段。所以在编写系统的说明文档时,根据系统所处的阶段来描述系统的内容。 绪论:这是对选题的背景,意…

PHP实践:用ThinkPHP6完整实现用户分页功能

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年6月CSDN上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师…

WPF Mvvm模式下面如何将事件映射到ViewModel层

前言 平常用惯了Command绑定,都快忘记传统的基于事件编程模式了,但是Commond模式里面有个明显的问题,就是你无法获取到事件源的参数。很多大聪明肯定会说,这还不简单,通过自己写控件,给控件加个自定义属性不就行了,想要啥事件就写啥事件进去,完全自主可控。但是对于写…

Day04:每日一题:2661. 找出叠涂元素

2661. 找出叠涂元素 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。 arr 和 mat 都包含范围 [1,m * n] 内的 所有 整数。从下标 0 开始遍历 arr 中的每个下标 i ,并将包含整数 arr[i] 的 mat 单元格涂色。请你找出 arr 中在 mat…

【数电笔记】逻辑代数的基本定律、常用公式

说明: 笔记配套视频来源:B站 逻辑代数的基本定律 1. 常量间的运算 2. 逻辑变量与常量的运算 3. 与普通代数相似的定律 4. 摩根定律(反演律) 5. 等式证明方法例题 逻辑代数的常用公式 1. 吸收律 2. 冗余律 3. 示例应用 4. 关于异…

stm32的中断复习

(https://img-blog.csdnimg.cn/4fa918bc1ebb41118410bc9a41d4b296.jpg)

lxml 总结

xm 和 lxml库 哪个更好用点 1. 性能: lxml 通常比 xml.etree.ElementTree 更快。lxml 使用了 C 编写的底层解析器,因此在处理大型 XML 文档时可能更高效。 如果性能对你的应用很重要,特别是在处理大型 XML 文件时,选择 lxml 可能…

shell编程系列(9)-使用cut选择列

文章目录 前言使用cut选择列选择特定的列 结语 前言 前面的文章介绍了sed命令,sed可以帮我们处理文本列,这边文章介绍cut命令,cut命令可以帮我们选择想要的列,在文本处理时候结合sed命令,就可以精准定位了。 cut命令是…

前端面试高频考点—TCP vs UDP

目录 简介: 区别: 应用选择: tcp为什么需要三次握手? 简介: TCP(传输控制协议)和UDP(用户数据报协议) TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,是专门为了在不…

代码随想录算法训练营第三十六天| 435 无重叠区间 763 划分字母区间 56 合并区间

目录 435 无重叠区间 763 划分字母区间 56 合并区间 435 无重叠区间 将intervals数组按照左端点进行升序排序。 设置变量len标志此时新加入端点后所有区间的位置,将其赋初值为第一对区间的右端点,因为该点是一定可达的。设置变量res来存储需要移除空间…

【C语言】与文件有关的操作

目录 1. 前言2. 什么是文件?2.1 程序文件2.2 数据文件2.3 文件名 3. 二进制文件和文本文件?4. 文件的打开和关闭4.1 流和标准流4.1.1 流4.1.2 标准流 4.2 文件指针4.3 文件的打开和关闭 5. 文件的顺序读写5.1 顺序读写函数介绍5.2 对比一组函数 6. 文件的…

制作太阳能小车

今天偶然星期想搞一个太阳能小车耍一下子,那么接下来就介绍下相关的准备物品吧 首先介绍下需要准备的物品: 1、玩具车拆下四个轮子 2、小马达一个 3、1.5v太阳能板(根据自己的需求购买相应的电压1.5v 3.7v 5v 12v等等) 4、3D打…

Android Studio新版UI介绍

顶部菜单栏 左侧主要菜单入口项目名称分支名称 展开之后,主要功能与原来菜单栏功能一样,最大的变化就是把setting独立出去了。 而项目名称这里,展开就可以看到打开的历史工程列表,可以直接新建工程,原来需要在项目名称…

学习DNS

文章目录 一、DNS介绍二、DNS架构三、DNS的原理四、DNS软件安装和配置文件4.1、DNS正向解析4.2、DNS反向解析4.3、多域DNS4.4、DNS主从配置 一、DNS介绍 DNS(Domain name system)。 域名:由特定的格式组成,用来表示互联网中某一台…

[FUNC]判断窗口在哪一个屏幕上

#Requires AutoHotkey v2.0#z:: { ToolTip "Notepad窗口所在显示屏是:" GetMonitor() } GetMonitor() {CoordMode("Mouse", "Screen"); MouseGetPos &mx, &myWinGetPos &mx, &my,,,"ahk_class Notepad"…