等待唤醒机制和阻塞队列
1. 等待唤醒机制
由于线程的随机调度,可能会出现“线程饿死”的问题:也就是一个线程加锁执行,然后解锁,其他线程抢不到,一直是这个线程在重复操作
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
等待(wait):当一个线程执行到某个对象的wait()方法时,它会释放当前持有的锁(如果有的话),并进入等待状态。此时,线程不再参与CPU的调度,直到其他线程调用同一对象的notify()或notifyAll()方法将其唤醒,类似的,wait() 方法也可以传入一个参数表示等待的时间,不加参数就会一直等
唤醒(notify/notifyAll):
notify: 唤醒在该对象监视器上等待的某个线程,如果有多个线程在等待,那么具体唤醒哪一个是随机的
notifyAll: 唤醒在该对象监视器上等待的所有线程
1.1. wait
上面的方法是Object提供的方法,所以任意的Object对象都可以调用,下面来演示一下:
public class ThreadDemo14 {public static void main(String[] args) throws InterruptedException {Object obj = new Object();System.out.println("wait前");obj.wait();System.out.println("wait后");}
}
结果抛出了一个异常:非法的锁状态异常,也就是调用wait的时候,当前锁的状态是非法的
这是因为,在wait方法中,会先解锁然后再等待,所以要使用wait,就要先加个锁,阻塞等待就是把自己的锁释放掉再等待,不然一直拿着锁等待,其他线程就没机会了
把wait操作写在synchronized方法里就可以了,运行之后main线程就一直等待中,在jconsole中看到的也是waiting的状态
注意:wait操作进行解锁和阻塞等待是同时执行的(打包原子),如果不是同时执行就可能刚解锁就被其他线程抢占了,然后进行了唤醒操作,这时原来的线程再去等待,已经错过了唤醒操作,就会一直等
wait执行的操作:1. 释放锁并进入阻塞等待,准备接收唤醒通知 2. 收到通知后唤醒,并重新尝试获得锁
1.2. notify
接下来再看一下notify方法:
public class ThreadDemo15 {private static Object lock = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (lock){System.out.println("t1 wait 前");try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1 wait 后");}});Thread t2 = new Thread(()->{synchronized (lock){System.out.println("t2 notify 前");Scanner sc = new Scanner(System.in);sc.next();//这里的输入主要是构造阻塞lock.notify();System.out.println("t2 notify 后");}});}
}
然后就会发现又出错了,还是之前的错误,notify也需要先加锁才可以
把之前的notify也加进synchornized就可以了,并且还需要确保是同一把锁
调用wait方法的线程会释放其持有的锁,被唤醒的线程在执行之前,必须重新获取被释放的锁
public class Cook extends Thread {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.count == 0) {break;} else {if (Desk.foodFlag == 0) {try {Desk.lock.wait();//厨师等待} catch (InterruptedException e) {e.printStackTrace();}} else {Desk.count--;System.out.println("还能再吃" + Desk.count + "碗");Desk.lock.notifyAll();//唤醒所有线程Desk.foodFlag = 0;}}}}}
}
public class Foodie extends Thread {@Overridepublic void run() {while (true) {synchronized (Desk.lock) {if (Desk.count == 0) {break;} else {if (Desk.foodFlag == 1) {try {Desk.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}} else {System.out.println("已经做好了");Desk.foodFlag = 1;Desk.lock.notifyAll();}}}}}
}
public class Desk {public static int foodFlag = 0;public static int count = 10;//锁对象public static Object lock = new Object();
}
这里实现的功能就是,厨师做好食物放在桌子上,美食家开始品尝,如果桌子上没有食物,美食家就等待,有的话,厨师进行等待
sleep() 和 wait() 的区别:
这两个方法看起来都是让线程等待,但是是有本质区别的,使用wait的目的是为了提前唤醒,sleep就是固定时间的阻塞,不涉及唤醒,虽然之前说的Interrupt可以使sleep提前醒来,但是Interrupt是终止线程,并不是唤醒,wait必须和锁一起使用,wait会先释放锁再等待,sleep和锁无关,不加锁sleep可以正常使用,加上锁sleep不会释放锁,抱着锁一起睡,其他线程无法拿到锁
在刚开始提到过,如果有多个线程都在同一个对象上wait,那么唤醒哪一个线程是随机的:
public class ThreadDemo16 {private static Object lock = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock) {System.out.println("t1 wait 前");try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t1 wait 后");}});Thread t2 = new Thread(() -> {synchronized (lock) {System.out.println("t2 wait 前");try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2 wait 后");}});Thread t3 = new Thread(() -> {synchronized (lock) {System.out.println("t3 wait 前");try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t wait 后");}});Thread t4 = new Thread(() -> {synchronized (lock) {System.out.println("t4 notify 前");Scanner sc = new Scanner(System.in);sc.next();lock.notify();System.out.println("t4 notify 后");}});t1.start();t2.start();t3.start();t4.start();}
}
这次只是t1被唤醒了
还可以使用notifyAll,把全部的线程都唤醒
2. 阻塞队列
2.1. 阻塞队列的使用
阻塞队列是一种特殊的队列,相比于普通的队列,它支持两个额外的操作:当队列为空时,获取元素的操作会被阻塞,直到队列中有元素可用;当队列已满时,插入元素的操作会被阻塞,直到队列中有空间可以插入新元素。
当阻塞队列满的时候,线程就会进入阻塞状态:
public class ThreadDemo19 {public static void main(String[] args) throws InterruptedException {BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(3);blockingDeque.put(1);System.out.println("添加成功");blockingDeque.put(2);System.out.println("添加成功");blockingDeque.put(3);System.out.println("添加成功");blockingDeque.put(4);System.out.println("添加成功");}
}
同时,当阻塞队列中没有元素时,再想要往外出队,线程也会进入阻塞状态
public class ThreadDemo20 {public static void main(String[] args) throws InterruptedException {BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(20);blockingDeque.put(1);System.out.println("添加成功");blockingDeque.put(2);System.out.println("添加成功");blockingDeque.take();System.out.println("take成功");blockingDeque.take();System.out.println("take成功");blockingDeque.take();System.out.println("take成功");}
}
2.2. 实现阻塞队列
根据阻塞队列的特性,可以尝试来自己手动实现一下
可以采用数组来模拟实现:
public class MyBlockingDeque {private String[] data = null;private int head = 0;private int tail = 0;private int size = 0;public MyBlockingDeque(int capacity) {data = new String[capacity];}
}
接下来是入队列的操作:
public void put(String s) throws InterruptedException {synchronized (this) {while (size == data.length) {this.wait();}data[tail] = s;tail++;if (tail >= data.length) {tail = 0;}size++;this.notify();}
}
由于设计到变量的修改,所以要加上锁,这里调用wait和notify来模拟阻塞场景,并且需要注意wait要使用while循环,如果说被Interrupted打断了,那么就会出现不可预料的错误
出队列也是相同的道理:
public String take() throws InterruptedException {
String ret = "";
synchronized (this) {while (size == 0) {this.wait();}ret = data[head];head++;if (head >= data.length) {head = 0;}size--;this.notify();
}
return ret;
}
3. 生产者消费者模型
生产者消费者模型是一种经典的多线程同步模型,用于解决生产者和消费者之间的协作问题。在这个模型中,生产者负责生产数据并将其放入缓冲区,消费者负责从缓冲区中取出数据并进行处理。生产者和消费者之间通过缓冲区进行通信,彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度,提高系统的可维护性和可扩展性。
而阻塞队列可以当做上面的缓冲区:
public class ThreadDemo21 {public static void main(String[] args) {BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(100);Thread t1 = new Thread(()->{int i = 1;while (true){try {blockingDeque.put(i);System.out.println("生产元素:" + i);i++;Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{while (true){try {int i = blockingDeque.take();System.out.println("消费元素:" + i);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}
如果说把sleep的操作放到线程2会怎么样?
线程一瞬间就把阻塞队列沾满了,后面还是一个线程生产,一个线程消费,虽然打印出来的有偏差
生产者和消费者之间通过缓冲区进行通信,彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度,提高系统的可维护性和可扩展性。
相关文章:

等待唤醒机制和阻塞队列
1. 等待唤醒机制 由于线程的随机调度,可能会出现“线程饿死”的问题:也就是一个线程加锁执行,然后解锁,其他线程抢不到,一直是这个线程在重复操作 void wait() 当前线程等待,直到被其他线程唤醒 void no…...

IO多路复用是如何处理多个客户端同时访问一个数据的
1. 原理概述 IO多路复用通过单个线程或进程监听多个文件描述符的状态变化,当某个文件描述符就绪(例如,有数据可读、可写或发生异常)时,线程或进程会收到通知,并对该文件描述符执行相应的IO操作。这种方式显…...

QT中使用UTF-8编码
在Qt中,确保应用程序使用UTF-8编码是非常重要的,尤其是在处理国际化和多语言文本时。以下是一些确保在Qt应用程序中使用UTF-8编码的方法: ### 1. 设置全局默认编码 在应用程序启动时,可以设置全局默认编码为UTF-8。这可以通过调…...

我对 monorepo 的一些思考
我对 monorepo 的一些思考 我对 monorepo 的一些思考 前言它的由来技术选型 管理工具语言与打包调试工具测试框架代码规范与质量控制本地引用与发包替换发包流程Github 相关配置部署 使用手册 功能特性总结如何使用?清除默认的包(可选)模板包介绍 packagesapps 更新…...

Java学习Day41:骑龙救!(springMVC)
springMVC与sevlet都是对应表现层web的,但是越复杂的项目使用SpringMVC越方便 基于Java实现MVC模型的轻量级web框架 目标: 小案例: 1.导入依赖 spring-context: 提供 Spring 框架的核心功能,如依赖注入、事件发布和其他应用上…...

Redis 常用命令总结
文章目录 目录 文章目录 1 . 前置内容 1.1 基本全局命令 KEYS EXISTS 编辑 DEL EXPIRE TTL TYPE 1.2 数据结构和内部编码 2. String类型 SET GET MGET MSET SETNX INCR INCRBY DECR DECYBY INCRBYFLOAT 命令小结 内部编码 3 . Hash 哈希类型 HSET …...

Mysql SqlServer 分页
一、MySQL分页 SELECT column1, column2 FROM table ORDER BY column1 LIMIT Offset, Fetch; SELECT column1, column2 FROM table WHERE id BETWEEN StartId AND EndId ORDER BY column1; 二、Sql Server 分页 SELECT column1, column2 FROM table ORDER BY column1 OFFSE…...

电子支付原理
电子支付原理 1.电子支付概述2.线下支付概念和一般流程线下支付技术分类 3.线上支付概念和一般流程 参考自:https://www.topsec.com.cn/uploads/2023-10-08/49dab9d0-004b-4955-808a-d1c83998b8191696745486491.pdf 1.电子支付概述 电子支付通用支付流程一般涉及四…...

什么是OAuth 2.0?OAuth 2.0的工作流程是什么?与OAuth 1.0有哪些区别?
在浏览网页时,你肯定会遇到允许你使用社交媒体账户登录的网站。此功能一般是使用流行的OAuth 2.0框架构建的。OAuth 2.0是对OAuth 1.0的彻底重写,OAuth 2.0与OAuth 1.0或1.1不向后兼容。 1. OAuth产生背景 为了更好的理解OAuth,我们假设有如…...

Unity+LeapMotion2的使用
开始吧 导入步骤1.到官网下载软件并安装2.安装插件3.场景中添加检测管理器4.场景中添加手部模型 更多细节 导入步骤 1.到官网下载软件并安装 地址 重启电脑后连接设备 可以看到连接成功 2.安装插件 (也可以看官方教程) Project—>PackageManag…...

【CanMV K230 AI视觉】 跌倒检测
【CanMV K230 AI视觉】 跌倒检测 跌倒检测 动态测试效果可以去下面网站自己看。 B站视频链接:已做成合集 抖音链接:已做成合集 跌倒检测 跌倒检测主要根据人体姿态来判断,可以用于老人、小孩跌倒监护。 实验名称:跌倒检测 实验…...

谈谈PCIe VID、DID、SSID、SSVID背后的智慧
PCIe Vendor ID 想了半天还是觉得从“ID是什么”这个问题开始比较好。那么ID是什么?ID就是身份。那身份又是什么?身份就是一个合理存在,用于区分不同个体。为什么叫“合理存在”呢?如果国家不给你发身份证,你就是黑户…...

9月11日
使用绘制事件完成钟表的绘制 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTcpSocket> #include<QMessageBox>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpub…...

昇腾310内存拷贝测试
目的 从服务器将数据拷贝到昇腾310 ai卡 结论 数据进入到服务器后的内存不能直接用昇腾acl接口拷贝到AI卡。 需要 1)先用acl接口申请内存; 2) 将数据拷贝到acl申请的内存 3)用acl接口将数据拷贝到AI卡 makefile # Copyri…...

‘$store‘ is not defined.
vueX不知道是否发挥作用?this.$store获取不到store/index.js中的数据 原因:版本问题 vuex版本不对,获取不到store 默认版本说明vuevue3vueXvue4vuex4只能在vue3中使用 vue默认vue3版本,vuex默认vuex4版本,vuex4只能…...

如何利用Linux提升工作效率和安全性?
Linux,作为一款自由和开放源代码的操作系统,已经在全球范围内得到了广泛的应用。无论是服务器、云计算、物联网,还是超级计算机,Linux都扮演着重要的角色。本文将深入探讨Linux的应用场景,以及如何利用Linux提升工作效…...

初始Linux 和 各种常见指令
目录 Linux背景 1. 发展史 Linux发展历史 1.历史 2. 开源 Linux下基本指令 01. ls 指令 02. pwd命令 03. cd 指令 04. touch指令 05.mkdir指令(重要): 06.rmdir指令 && rm 指令(重要): …...

【稀疏矩阵】使用torch.sparse模块
文章目录 稀疏矩阵的格式coocsrcsc Construction of Sparse COO tensorsConstruction of CSR tensorsLinear Algebra operations(稀疏与稠密之间混合运算)Tensor methods and sparse(与稀疏有关的tensor成员函数)coo张量可用的ten…...

如何增加谷歌网站曝光率?
增加谷歌网站曝光率其实就是让更多的人在搜索相关内容时,能看到你的网站。首先你就要搞清楚用户在搜索什么,这样才能把正确的内容呈现在他们面前。首先,你得站在用户的角度思考,想想他们在搜索与你网站相关的信息时,可…...

虚幻中的c++(持续更新)
文章目录 虚幻中的cUPROPERTY参数 UFUNCTION参数 虚幻中的c UPROPERTY 是虚幻中用于声明属性的宏,它用于标记某个属性是一个虚幻托管的属性,并且可以在编辑器中进行访问和操作。其提供了一系列参数,用于定义属性的各种行为,例如是…...

83-MySQL 索引有几种
MySQL中的索引主要有以下几种: 普通索引:最基本的索引类型,没有唯一性的限制,可以通过多个字段创建复合索引。 唯一索引:与普通索引类似,但区别在于唯一索引的每一个索引值只对应唯一的数据记录。 主键索…...

文献解读-The trans-omics landscape of COVID-19
关键词:流行病学;基因测序;变异检测; 文献简介 标题(英文):The trans-omics landscape of COVID-19 标题(中文):COVID-19的跨组学全景 发表期刊:…...

Unity核心实践小项目
要源码包的私信我。 简介 衔接Unity核心学习后的实操小项目 需求分析 准备工作 面板基类 为了能够控制一画布整体的透明度,所以需要给每个面板都添加一个 CanvasGroup组件 UI管理器 UGUI方面的参数设置 开始场景 场景搭建 直接用资源包搭建好的场景:…...

Avaloia 实现国产麒麟系统中文显示界面
最近在搞一个国产麒麟系统的接口对接,因为,接口内含复杂的签名验证,而且还是离线环境,所以,postman不是很好用。 就想着哪个方式好一些,主要是有选择图片的操作,所以,在Electron和A…...

pytest 生成allure测试报告
allure的安装 github地址 allure资产列表 windows下载.zip,解压并配置环境变量PATH;linux下载tar.gz,解压配置; allure作为pytest插件 # 安装 pip install allure-pytest# 执行单元测试,生成allure测试数据&…...

查询GPU版本以及PyTorch中使用单GPU和多GPU
文章目录 多GPU介绍GPU可用性及版本检查使用单个GPU使用多个GPU多GPU介绍 多GPU是指使用多个显卡来同时进行计算,以加速深度学习模型的训练和推断。每个GPU都有自己的内存和计算能力,通过同时利用多个GPU可以并行地执行模型的计算,从而提高整体的计算效率。 GPU可用性及版…...

基于SpringBoot+Vue的线上考试系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的线上考试…...

动手学深度学习(pytorch土堆)-02TensorBoard的使用
1.可视化 代码使用了 torch.utils.tensorboard 将数据记录到 TensorBoard 以便可视化。具体来说,它将标量数据记录到目录 logs 中,使用的是 SummaryWriter 类。 代码分解如下: SummaryWriter("logs"):初始化一个 Ten…...

STM3学习记录
一、串口 1.串口定义,将串口相关寄存器的首地址强制转化为串口结构体,方便通过结果体访问串口的寄存器 #define __IO volatile /*!< Defines read / write permissions */ typedef struct {__IO uint32_t SR; /*!< US…...

【网络】应用层协议-http协议
应用层协议-http协议 文章目录 1.Http协议1.1什么是http协议1.2认识URL1.3urlencode和urldecode1.4HTTP请求协议格式1.5HTTP响应协议格式1.6HTTP常见的Header1.7HTTP常见状态码1.8HTTP的方法1.8根据url调取对应的服务 2.cookie和session2.1cookie2.2session 3.HTTPS协议3.1对称…...