山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(二十)- JUC(6)

目录

wait , notify

wait vs sleep

正确使用方法

同步保护性暂停

join的源码

Future

异步生产者/消费者模型

定义

Park & Unpark

原理


wait , notify

小故事小南需要烟才能工作,但它又要占这锁让别人无法进来。那么这个时候开一个waitSet相当于就是休息室让小南进去。并且释放锁。如果烟到了,那么notify小南就能够继续工作了。

Blocked和Waiting区别其实就是waiting是释放了锁,blocked是没有锁waiting被notify之后仍然需要进入到entrylist进行等待。

  @Slf4j(topic = "c.TestWaitNotify")public class Test {​// 锁对象final static Object obj = new Object();​public static void main(String[] args) throws InterruptedException {​new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t1").start();​new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}},"t2").start();​// 主线程两秒后执行Thread.sleep(5000);log.debug("唤醒 obj 上其它线程");synchronized (obj) {//            obj.notify(); // 唤醒obj上一个线程obj.notifyAll(); // 唤醒obj上所有等待线程}}}
  20:17:53.579 [t1] DEBUG c.TestWaitNotify - 执行....20:17:53.581 [t2] DEBUG c.TestWaitNotify - 执行....20:17:58.584 [main] DEBUG c.TestWaitNotify - 唤醒 obj 上其它线程20:17:58.584 [t2] DEBUG c.TestWaitNotify - 其它代码....20:17:58.584 [t1] DEBUG c.TestWaitNotify - 其它代码....​进程已结束,退出代码0

wait vs sleep

sleep:Thread调用,静态方法,而且不会释放锁

wait:所有obj,但是要配合synchronize使用,可以释放锁

sleep在睡眠时,不会释放锁,wait会释放对象锁

通常锁会加上final防止被修改

正确使用方法

小南需要烟才能工作,如果是使用sleep不释放锁,那么其他需要等待干活的人就会干等着,等烟来。但是wait可以让小南释放锁,让其他线程工作,并且唤醒小南

  @Slf4j(topic = "c.TestCorrectPosture")public class Test {static final Object room = new Object();​// 有无烟static boolean hasCigarette = false;static boolean hasTakeout = false;​public static void main(String[] args) throws InterruptedException {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会!");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}}}, "小南").start();​for (int i = 0; i < 5; i++) {new Thread(() -> {synchronized (room) {log.debug("可以开始干活了");}}, "其它人").start();}​Thread.sleep(1000);// 送烟线程new Thread(() -> {synchronized (room) {hasCigarette = true;log.debug("烟到了噢!");}}, "送烟的").start();}​}
  20:32:22.014 [小南] DEBUG c.TestCorrectPosture - 有烟没?[false]20:32:22.019 [小南] DEBUG c.TestCorrectPosture - 没烟,先歇会!20:32:24.024 [小南] DEBUG c.TestCorrectPosture - 有烟没?[false]20:32:24.024 [送烟的] DEBUG c.TestCorrectPosture - 烟到了噢!20:32:24.024 [其它人] DEBUG c.TestCorrectPosture - 可以开始干活了20:32:24.024 [其它人] DEBUG c.TestCorrectPosture - 可以开始干活了20:32:24.024 [其它人] DEBUG c.TestCorrectPosture - 可以开始干活了20:32:24.024 [其它人] DEBUG c.TestCorrectPosture - 可以开始干活了20:32:24.024 [其它人] DEBUG c.TestCorrectPosture - 可以开始干活了​进程已结束,退出代码0

存在的问题 :

  1. 其它干活的线程,都要一致阻塞,效率低

  2. 就算烟提前送到,也无法立刻醒来

  3. 送烟加上锁之后,相当于门一直锁着,烟送不进去

改进 :

  @Slf4j(topic = "c.TestCorrectPosture")public class Test {static final Object room = new Object();​// 有无烟static boolean hasCigarette = false;static boolean hasTakeout = false;​public static void main(String[] args) throws InterruptedException {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}}}, "小南").start();​for (int i = 0; i < 5; i++) {new Thread(() -> {synchronized (room) {log.debug("可以开始干活了");}}, "其它人").start();}​Thread.sleep(1000);// 送烟线程new Thread(() -> {synchronized (room) {hasCigarette = true;log.debug("烟到了噢!");room.notify();    // 叫醒小南线程}}, "送烟的").start();}​}

存在问题

会不会有其他线程在等待着锁?如果是那么会不会唤醒错了线程?(虚假唤醒)

解决 :

可以通过while多次判断条件是否成立,直接使用notifyAll来唤醒所有的线程。然后线程被唤醒之后先再次判断条件是否成立,成立那么往下面执行,如果不成立那么继续执行wait。

  while (!hasCigarette) {log.debug("没烟,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}

正确使用 :

  synchronized(lock){while(条件不成立){lock.wait();}   // 干活}​// 另一个线程synchronized(lock){lock.notifyAll();}

同步保护性暂停

定义

  • t1需要t2的结果,那么就可以通过一个中间对象guardedObject来充当这个中间商,t2执行完就发送消息到obj,然后obj交给t1

  • 如果是不断发送结果那么可以使用消息队列

  • 要等待所以是同步

  • join和future就是用的这个原理

  public class Test {public static void main(String[] args) {GuaObj guaObj = new GuaObj();Thread thread = new Thread(() -> {System.out.println("锁住,等待结果");guaObj.get(2000);System.out.println("解锁");}, "t1");thread.start();​​Thread thread1 = new Thread(() -> {System.out.println("先睡两秒");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("解锁,设置对象");guaObj.set(new Object());}, "t2");thread1.start();}}​class GuaObj{// 结果public Object response;​// 获取结果// timeout表示最多等多久public Object get(long timeout){synchronized (this){// 开始时间long cur = System.currentTimeMillis();// 经历的时间long paseTime=0;while(response==null){try {// 这一轮应该等的时间long waitTime=timeout-paseTime;//超时就不等了if(waitTime<=0) break;this.wait(waitTime);paseTime=System.currentTimeMillis()-cur;} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("等待结束");return response;}}​// 产生结果public void set(Object response){synchronized (this){this.response=response;this.notifyAll();}}}
  锁住,等待结果先睡两秒解锁,设置对象等待结束解锁​进程已结束,退出代码0
  • 需要记录超时的时间,并且重新设置waittime,原因是可能会有虚假唤醒,那么这个时候超时时间不是timeout而是timeout-passedTime,也就是线程执行的时间。

  • 如果超时的话,那么就会自动结束

join的源码

  public final synchronized void join(long millis)throws InterruptedException {//一开始的时间long base = System.currentTimeMillis();//线程执行的时间long now = 0;​//如果是<0那么就抛出异常if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}​//如果是0那么就一直等待线程执行完,isAlive是否生存if (millis == 0) {while (isAlive()) {wait(0);}} else {//timeout超时那么就结束while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

Future

相当于就是一个信箱,里面装了很多GuardObject对象,线程可以通过对应的地址访问对象获取结果

异步生产者/消费者模型

定义

相当于就是生产者给队列生产结果,消费者负责处理结果

  • 不需要一一对应

  • 平衡资源

  • 消息队列有容量控制

  • 阻塞队列控制结果出队列

  public class Test {public static void main(String[] args) {MesageQueue queue = new MesageQueue(2);​for (int i = 0; i < 3; i++) {int id = i;new Thread(() -> {queue.set(new Message(id, "值" + id));},"生产者" + i).start();}​new Thread(() -> {while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}Message message = queue.take();}}, "消费者").start();}}​​@Slf4jclass MesageQueue{//存消息的集合private LinkedList<Message> list = new LinkedList();// 消息容量private int capacity;​public MesageQueue(int capacity){this.capacity = capacity;}​// 获取消息public  Message take()  {// 检查队列是否为空synchronized (list){while(list.isEmpty()){try {log.debug("队列为空,消费者线程等待");list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}Message message = list.removeFirst();log.debug("已经消费了消息 {}",message);list.notifyAll();return message;}}​// 存入消息public void set(Message message) {// 检查是不是满了synchronized (list){while(list.size() == capacity){try {log.debug("队列已满,生产者线程等待");list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}list.addLast(message);log.debug("已经生产了消息 {}",message);list.notifyAll();}}}​// 消息类final class  Message{private int id;private Object value;​public Message(int id, Object value){this.id = id;this.value = value;}​public int getId() {return id;}​public Object getValue() {return value;}​@Overridepublic String toString() {return "Message{" +"id=" + id +", value=" + value +'}';}}
  12:58:24.373 [生产者1] DEBUG MesageQueue - 已经生产了消息 Message{id=1, value=值1}12:58:24.375 [生产者2] DEBUG MesageQueue - 已经生产了消息 Message{id=2, value=值2}12:58:24.377 [生产者0] DEBUG MesageQueue - 队列已满,生产者线程等待12:58:25.371 [消费者] DEBUG MesageQueue - 已经消费了消息 Message{id=1, value=值1}12:58:25.371 [生产者0] DEBUG MesageQueue - 已经生产了消息 Message{id=0, value=值0}12:58:26.386 [消费者] DEBUG MesageQueue - 已经消费了消息 Message{id=2, value=值2}12:58:27.397 [消费者] DEBUG MesageQueue - 已经消费了消息 Message{id=0, value=值0}12:58:28.405 [消费者] DEBUG MesageQueue - 队列为空,消费者线程等待

Park & Unpark

与wait和notify的区别

  • 不需要与monitor一起使用

  • 可以精准唤醒和阻塞线程

  • 可以先unpark,但是不能先notify。但是unpark之后park不起作用。

原理

①park,先去到counter里面判断是不是0,如果是那么就让线程进入队列。接着就是把counter设置为0

②unpark,那么唤醒线程,恢复运行,并且把counter设置为1

③先unpark后park,那么就unpark补充counter为1,那么park判断counter是1,认为还有体力可以继续执行。

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

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

相关文章

一文讲解——Java多态

目录 一、什么是多态&#xff1f;二、转型向上转型向下转型 三、方法覆盖与方法重载四、绑定动态绑定静态绑定 五、理解多态 一、什么是多态&#xff1f; 多态的词组字面意思是&#xff1a; 某种事物多种形态。 但是对于我们学习Java 的程序原来说&#xff0c;就不不能简单这样…

Weblogic XML反序列化漏洞 [CVE-2017-10271]

漏洞环境搭建请参考 http://t.csdnimg.cn/i11e2 漏洞原理 Weblogic的wls security组件对外提供webservice服务&#xff0c;wls security组件使用了xmldecoder来解析用户传入的xml数据&#xff0c;如果用户进行xml恶意数据的构造&#xff0c;即可触发反序列化漏洞 漏洞版本 O…

CentOS 7.9 邮箱部署——Postfix+Dovecot详细

PostfixDovecot 文章目录 PostfixDovecot资源列表基础环境一、部署DNS二、部署postfix和dovecot2.1、配置postfix2.2、配置dovecot2.3、创建邮件用户 三、发送邮件测试3.1、windows安装poxmail3.2、登录邮箱3.3、发送接收邮件 四、搭建SSL认证加密4.1、生成私钥4.2、生成公钥4.…

贪心算法4(c++)

过河的最短时间 题目描述 输入 在漆黑的夜里&#xff0c;N位旅行者来到了一座狭窄而且没有护栏的桥边。如果不借助手电筒的话&#xff0c;大家是无论如何也不敢过桥去的。不幸的是&#xff0c;N个人一共只带了一只手电筒&#xff0c;而桥窄得只够让两个人同时过&#xff0c;如果…

YOLOv8_pose预测流程-原理解析[关键点检测理论篇]

YOLOv8_seg的网络结构图在博客YOLOv8网络结构介绍_CSDN博客已经更新了,由网络结构图可以看到相对于目标检测网络,实例分割网络只是在Head层不相同,如下图所示,在每个特征层中增加了KeyPoint分支(浅绿色),通过两个卷积组和一个Conv卷积得到得到通道数为51的特征图,51表示…

window环境下QT5开发环境的搭建

1、安装visual Stusio 15 生成工具2012 2、安装Visual studio Enterprise 2017 3、Visual studio Enterprise 2017安装完成之后&#xff0c; 修改&#xff1a;选择桌面调试&#xff0c;如下&#xff1a; 4、打开QTcreator&#xff0c;选项中&#xff0c;配置编译器&#xff…

摸鱼大数据——Hive基础理论知识——Hive环境准备

Hive环境准备 1、shell脚本执行方式 方式1: sh 脚本 注意: 需要进入脚本所在目录,但脚本有没有执行权限不影响执行 方式2: ./脚本 注意: 需要进入脚本所在目录,且脚本必须有执行权限 方式3: /绝对路径/脚本 注意: 不需要进入脚本所在目录,但必须有执行…

线程池,日志

所要用到的知识点&#xff1a; 多线程的创建 生产消费模型&#xff0c; 线程锁 条件变量 代码&#xff1a; 线程池日志

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示与单片机连接的按键的按键值的功能

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示与单片机连接的按键的按键值应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍TM1638键盘数码…

C++面向对象程序设计 - 输入和输出

程序的输入指的是文件将数据传送给程序&#xff0c;程序的输出指的是从程序将数据传送输出文件。 C的输入和和输出包括以下三个方面&#xff1a; 对系统指定的标准设备的输入和输出&#xff0c;即从键盘输入数据&#xff0c;输出到显示器屏幕。以外存磁盘&#xff08;或光盘、…

gdc2024:Raytracing in Snowdrop技术实现与性能优化策略

在今年的GDC&#xff08;游戏开发者大会&#xff09;的Advanced Graphics Summit上&#xff0c;关于Snowdrop引擎中光线追踪技术的讨论引起了广泛关注。 一、光线追踪全局照明的实现细节 屏幕空间追踪&#xff1a; 屏幕空间追踪从相机出发&#xff0c;对屏幕上的每个像素点生成…

C++ 红黑树

目录 1.红黑树的概念 2.红黑树的性质 3.红黑树节点的定义 4.红黑树的插入操作 5.数据测试 1.红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个…

C++基础与深度解析 | 泛型算法 | bind | Lambda表达式

文章目录 一、泛型算法1.泛型算法的分类2.迭代器分类 二、bind与lambda表达式1.bind2.lambda表达式 三、泛型算法的改进--ranges(c20) 一、泛型算法 C中的泛型算法是标准模板库&#xff08;STL&#xff09;的一部分&#xff08;这里重点讨论 C 标准库中定义的算法&#xff0c;而…

【vue-cli搭建vue项目的过程2.x】

vue-cli搭建vue项目 vue-cli搭建vue项目安装node安装vue-cli脚手架并创建项目安装 Ant Design Vue或element-ui(笔者使用Ant-design-vue组件&#xff0c;并全局引入)开发安装三方库包1、Package.json文件---引入如下package.json文件执行npm i或npm install命令即可下载如下依赖…

数据结构~~链式二叉树

目录 一、基本概念 链式存储概念 二、链式二叉树的结构 链式二叉树结构 构建链式二叉树 二叉树的遍历 二叉树节点和高度等 二叉树销毁 三、链式二叉树的练习 相同的树 对称二叉树 另外一颗子树 二叉树前序遍历 二叉树遍历 四、完整代码 Tree.h Tree.c 五、总结 一…

Linux服务升级:Predixy 升级代理 Redis-cluster 集群

目录 一、实验 1.环境 2. 启动Redis服务 3.Predixy 升级代理 Redis-cluster 集群 二、问题 1. Predixy进行set操作报错 2.如何创建脚本启动predixy 3.Redis代理对比 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统版本节点软件IP备注CentOS7.9Redis…

Springboot开发 -- Postman 调试类型详解

引言 在 Spring Boot 应用开发过程中&#xff0c;接口测试是必不可少的一环。Postman 作为一款强大的 API 开发和测试工具&#xff0c;可以帮助开发者轻松构建、测试和管理 HTTP 请求。本文将为大家介绍如何在 Spring Boot 开发中使用 Postman 进行接口测试。 一、准备工作 安…

C/C++|malloc分配内存详解

看本节前&#xff0c;希望读者有linux内存分布的基本概念&#xff0c;可以阅读这篇文章&#xff1a; 进程虚拟地址空间和函数调用栈 在本节中希望读者可以一口气阅读完所有内容。 本博客内容全部来自小林coding&#xff1a;malloc 是如何分配内存的&#xff1f; 这里仅为笔记记…

Linux/Ubuntu 中安装 ZeroTier,实现内网穿透,2分钟搞定

相信很多人都有远程连接家中设备的需求&#xff0c;如远程连接家中的NAS、Windows等服务&#xff0c;所以会涉及到一个内网穿透工具的使用&#xff0c;如果没有公网IP的情况下&#xff0c;推荐大家使用ZeroTier&#xff0c;这是一款强大的内网穿透工具。 mac和windows版的操作…

Nginx-狂神说

Nginx概述 公司产品出现瓶颈&#xff1f; 我们公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#xff0c;所以在低并发的情况下&#xff0c;一个jar包启动应用就够了&#xff0c;然后内部tomcat返回内容给用户。 但是慢慢的&#xff0c;使用我们平台…