当前位置: 首页 > news >正文

等待唤醒机制和阻塞队列

在这里插入图片描述

 

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. 等待唤醒机制 由于线程的随机调度&#xff0c;可能会出现“线程饿死”的问题&#xff1a;也就是一个线程加锁执行&#xff0c;然后解锁&#xff0c;其他线程抢不到&#xff0c;一直是这个线程在重复操作 void wait() 当前线程等待&#xff0c;直到被其他线程唤醒 void no…...

IO多路复用是如何处理多个客户端同时访问一个数据的

1. 原理概述 IO多路复用通过单个线程或进程监听多个文件描述符的状态变化&#xff0c;当某个文件描述符就绪&#xff08;例如&#xff0c;有数据可读、可写或发生异常&#xff09;时&#xff0c;线程或进程会收到通知&#xff0c;并对该文件描述符执行相应的IO操作。这种方式显…...

QT中使用UTF-8编码

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

我对 monorepo 的一些思考

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

Java学习Day41:骑龙救!(springMVC)

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

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.线上支付概念和一般流程 参考自&#xff1a;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有哪些区别?

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

Unity+LeapMotion2的使用

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

【CanMV K230 AI视觉】 跌倒检测

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

谈谈PCIe VID、DID、SSID、SSVID背后的智慧

PCIe Vendor ID 想了半天还是觉得从“ID是什么”这个问题开始比较好。那么ID是什么&#xff1f;ID就是身份。那身份又是什么&#xff1f;身份就是一个合理存在&#xff0c;用于区分不同个体。为什么叫“合理存在”呢&#xff1f;如果国家不给你发身份证&#xff0c;你就是黑户…...

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&#xff09;先用acl接口申请内存&#xff1b; 2&#xff09; 将数据拷贝到acl申请的内存 3&#xff09;用acl接口将数据拷贝到AI卡 makefile # Copyri…...

‘$store‘ is not defined.

vueX不知道是否发挥作用&#xff1f;this.$store获取不到store/index.js中的数据 原因&#xff1a;版本问题 vuex版本不对&#xff0c;获取不到store 默认版本说明vuevue3vueXvue4vuex4只能在vue3中使用 vue默认vue3版本&#xff0c;vuex默认vuex4版本&#xff0c;vuex4只能…...

如何利用Linux提升工作效率和安全性?

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

初始Linux 和 各种常见指令

目录 Linux背景 1. 发展史 Linux发展历史 1.历史 2. 开源 Linux下基本指令 01. ls 指令 02. pwd命令 03. cd 指令 04. touch指令 05.mkdir指令&#xff08;重要&#xff09;&#xff1a; 06.rmdir指令 && rm 指令&#xff08;重要&#xff09;&#xff1a; …...

【稀疏矩阵】使用torch.sparse模块

文章目录 稀疏矩阵的格式coocsrcsc Construction of Sparse COO tensorsConstruction of CSR tensorsLinear Algebra operations&#xff08;稀疏与稠密之间混合运算&#xff09;Tensor methods and sparse&#xff08;与稀疏有关的tensor成员函数&#xff09;coo张量可用的ten…...

如何增加谷歌网站曝光率?

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

虚幻中的c++(持续更新)

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

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...