多线程代码案例之阻塞队列

目录

1.生产者消费者模型

2.使用标准库中的阻塞队列

3.模拟实现阻塞队列


在介绍阻塞队列之前,会先介绍一些前置知识,像队列:有普通队列、优先级队列、阻塞队列、和消息队列。前面两个是线程不安全的,而后面两个是线程安全的。本文重点介绍阻塞队列。

1.生产者消费者模型

1.1队列功能介绍

(1)阻塞队列

1)当队列为空时,尝试出队列;此时,队列会阻塞等待,直到队列不为空才继续执行出队列操作。

2)当队列为满时,尝试入队列;此时,队列就会阻塞等待,直到队列不为满为止才能继续执行入队操作。

(2)消息队列

并非遵循常规的先进先出,而是带有一个topic关键字。当出队时指定某个topic,就会先出topic下的元素(topic内部就遵循先进先出)

举例:例如到医院窗口排队,有很多种类型的窗口,如:妇科、儿科、骨科等等(这些称为topic),不是说,你先来了就一定可以就诊,而是等待你所在的topic是否呼唤你。

像上面的阻塞队列和消息队列,起到的作用就是可以实现:生产者消费者模型。

1.2.生产者消费者模型介绍

(1)什么是生产者消费者模型

1)A线程进行入队操作,B线程进行出队操作。当队列为空时,B线程需要等待A线程入队,才能从队列中取出元素;当队列满时,A线程需要等待B线程取出元素后,才能继续入队。这里的A线程就相当于生产者,B线程相当于消费者。

2)有一个自动售卖机(相当于阻塞队列/消息队列),商人负责填货(生产者),用户负责买东西(消费者)。

(2)模型的作用

1)可以让程序机械能解耦合操作

2)可以程序“削峰填谷”

解耦合作用举例:

1.如果A和B是互相调用的关系,那么如果A中需要修改,那么B中的大部分都需要同步修改,否则无法互相调用。

2.当A和B中间加了一个队列,那么A与B的交互只需要通过操作队列即可。即使其中一个出现了问题,也不会影响到另外一个。

削峰填谷举例:

1.当服务器直接和客户端交互时,当请求过多时,就会直接导致服务器崩溃。

2.如果在客户端和服务器中间加上一个队列,让他们通过队列进行交互。即使请求再多,也不会影响到服务器,最坏的情况也就是队列崩溃。

所以说,阻塞队列/消息队列,就是可以实现生产者消费者模型的效果。

2.使用标准库中的阻塞队列

现在,我们介绍如何调用标准库中的阻塞队列。

2.1.创建阻塞队列

(1)选择正确的接口

(2)实例化的对象

可以选择的有下面这三个,很明显,它们之间只是基于不同的数据结构进行实现。

这三个,我们都是可以选择的。

(3)队列的操作

因为是队列,我们只需要考虑入队和出队操作即可。

在阻塞队列中,只有这两个是带有阻塞功能的,所以我们只需要使用这两个即可。

因为这两个操作,是带有阻塞功能的,也就是wait,所以使用时需要声明异常。

(4)普通的操作

这里普通的操作指的是在一个线程中进行操作。

程序运行起来,发现没有任何的报错,只是程序仍然不会结束,这就是阻塞功能。

2.2.使用阻塞队列

(1)消费者消费的很慢

当消费者消费慢时,也就是让消费者每次sleep,此时,就会产生生产者在等消费者的过程

 public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);Thread t1 = new Thread(()->{//负责入队操作for (int i = 0; i < 5000; i++) {try {queue.put(i);System.out.println("入队:"+i);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{//负责出队操作for (int i = 0; i < 5000; i++) {try {int tmp = queue.take();System.out.println("出队:"+tmp);Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}

(2)生成者生成的很慢

以上就是对阻塞队列的使用,下面我们自己实现一个阻塞队列

3.模拟实现阻塞队列

要模拟阻塞队列,就需要有入队/出队操作,并且可以进行阻塞等待,并且是线程安全的!

我们先从普通队列开始,然后加上线程安全和阻塞操作,最后进行优化和线程安全Pro版 

3.1.实现普通的队列

下面按照循环队列的形式进行创建队列,只提供了入队和出队操作

class MyBlockQueue {int head = 0;int tail = 0;int size = 0;public String[] elem;public MyBlockQueue(int capacity) {elem = new String[capacity];}//入队public void put(String s) throws InterruptedException {    if(size == elem.length) {return;}elem[tail] = s;//队尾入size++;if(tail >= elem.length-1) tail=0;else tail++;}}//出队public String take() throws InterruptedException {if(size == 0) {return null;}String tmp = elem[head];size--;if(head == elem.length-1) head = 0;else head++;return tmp;}
}
3.2.加上阻塞功能

这里我们使用的是wait而不是sleep。

class MyBlockQueue {int head = 0;int tail = 0;int size = 0;public String[] elem;public MyBlockQueue(int capacity) {elem = new String[capacity];}//入队public void put(String s) throws InterruptedException {synchronized (this) {if(size == elem.length) {System.out.println("队列满,阻塞等待");this.wait();}elem[tail] = s;//队尾入size++;if(tail >= elem.length-1) tail=0;else tail++;this.notify();//入队一个,唤醒一次}}//出队public String take() throws InterruptedException {synchronized (this) {if(size == 0) {System.out.println("队列空");this.wait();}String tmp = elem[head];size--;if(head == elem.length-1) head = 0;else head++;this.notify();//出队一个,唤醒一次return tmp;}}
}

(1)改进1:对入队、出队操作,都进行了加锁操作

(2)改进2:在队列满/空时,不进行return,而是进行阻塞等待;当有一个元素入队/出队之后,就进行唤醒一次。

上述不使用sleep的原因是:sleep是抱着锁使线程进入休眠状态,当此时有其他的操作(入队/出队)时,无法拿到锁,从而无法执行(发生了死锁)

上述还有一个缺点:就是wait不仅仅可以被notify唤醒,还可以被interrupt唤醒,所以要循环进行判断。

class MyBlockQueue {int head = 0;int tail = 0;int size = 0;public String[] elem;public MyBlockQueue(int capacity) {elem = new String[capacity];}//入队public void put(String s) throws InterruptedException {synchronized (this) {while (size >=elem.length) {//循环等待确认,防止不是被notify唤醒try {this.wait();}catch (InterruptedException e) {e.printStackTrace();}}elem[tail] = s;//队尾入size++;if(tail >= elem.length-1) tail=0;else tail++;this.notify();}}//出队public String take() throws InterruptedException {synchronized (this) {while (size ==0) {try {this.wait();}catch (InterruptedException e) {e.printStackTrace();}}String tmp = elem[head];size--;if(head == elem.length-1) head = 0;else head++;this.notify();return tmp;}}
}

(3)改进3:使用while+wait的方式反复确认是否要被唤醒

3.3.确保线程一定安全

上述线程其实已经很安全了,但是还需要再进一步优化,达到更安全的效果。对于线程安全还有两个:内存可见性问题和指令重排序,所以我们只需要对变量加上volatile关键字即可。

volatile int head = 0;
volatile int tail = 0;
volatile int size = 0;

上述就是一个完整的阻塞队列模拟实现的代码,下面展示完整代码:

class MyBlockQueue {/* int head = 0;int tail = 0;int size = 0;*/volatile int head = 0;volatile int tail = 0;volatile int size = 0;public String[] elem;public MyBlockQueue(int capacity) {elem = new String[capacity];}//入队public void put(String s) throws InterruptedException {synchronized (this) {/* if(size == elem.length) {System.out.println("队列满,阻塞等待");this.wait();}*/while (size >=elem.length) {//循环等待确认,防止不是被notify唤醒try {this.wait();}catch (InterruptedException e) {e.printStackTrace();}}elem[tail] = s;//队尾入size++;if(tail >= elem.length-1) tail=0;else tail++;this.notify();}}//出队public String take() throws InterruptedException {synchronized (this) {/* if(size == 0) {System.out.println("队列空");this.wait();}*/while (size ==0) {try {this.wait();}catch (InterruptedException e) {e.printStackTrace();}}String tmp = elem[head];size--;if(head == elem.length-1) head = 0;else head++;this.notify();return tmp;}}
}

测试代码:

 public static void main(String[] args) throws InterruptedException {MyBlockQueue myBlockQueue = new MyBlockQueue(10);Thread t1 = new Thread(()->{for (int i = 0; i < 20; i++) {try {System.out.println("生成1:");myBlockQueue.put("1");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{for (int i = 0; i < 30; i++) {String tmp = null;try {tmp = myBlockQueue.take();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("消费:"+tmp);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}

测试结果:

结果是可以的,和标准库中的阻塞队列基本一致。

几个注意事项:


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

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

相关文章

学生管理系统控制台版(java)

首先得先写个Student类&#xff0c;用来存放学生信息 public class Student {private String id;private String name;private int age;private String address;public Student() {}public Student(String id, String name, int age, String address) {this.id id;this.name…

2024年4月8日腾讯云故障复盘及情况说明

2024年4月8日15点23分&#xff0c;腾讯云团队收到告警信息&#xff0c;云API服务处于异常状态&#xff1b;随即在腾讯云工单、售后服务群以及微博等渠道开始大量出现腾讯云控制台登录不上的客户反馈。 经过故障定位发现&#xff0c;客户登录不上控制台正是由云API异常所导致。云…

极狐GitLab 如何在 helm 中恢复数据

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何在极狐GitLab …

关于部署ELK和EFLK的相关知识

文章目录 一、ELK日志分析系统1、ELK简介1.2 ElasticSearch1.3 Logstash1.4 Kibana&#xff08;展示数据可视化界面&#xff09;1.5 Filebeat 2、使用ELK的原因3、完整日志系统的基本特征4、ELK的工作原理 二、部署ELK日志分析系统1、服务器配置2、关闭防火墙3、ELK ElasticSea…

李廉洋;4.12现货黄金,美原油最新走势分析及策略。

现货黄金在美盘末将历史新高刷至2377美元/盎司。美国3月份PPI指数较上年同期上升2.1%&#xff0c;为11个月来的最高增幅&#xff0c;这份数据加之此前火爆的CPI指数&#xff0c;为美联储实现2%目标所面临的坎坷之路奠定了基础。不过&#xff0c;PPI报告中的细节让担心通胀再度加…

Python快速获取编程问题答案的方法库之howdoi使用详解

概要 howdoi是一个命令行工具,它提供了一种快速获取编程问题答案的方法,通过搜索和抓取Stack Overflow等网站的内容,直接在终端中显示编程问题的解决方案。 安装 通过pip可以轻松安装howdoi: pip install howdoi特性 快速访问编程解决方案:无需手动浏览Stack Overflow。…

ThingsBoard通过服务端获取客户端属性或者共享属性

MQTT基础 客户端 MQTT连接 通过服务端获取属性值 案例 1、首先需要创建整个设备的信息&#xff0c;并复制访问令牌 ​2、通过工具MQTTX连接上对应的Topic 3、测试链接是否成功 4、通过服务端获取属性值 5、在客户端查看对应的客户端属性或者共享属性的key 6、查看整个…

ELK 日志分析系统

目录 一. ELK 相关知识 1. ELK 的概念与组件 1.1 ElasticSearch&#xff1a; 1.2 Kibana&#xff1a; 1.3 Logstash&#xff1a; 可以添加的其它组件&#xff1a; Filebeat&#xff1a; 缓存/消息队列&#xff08;redis、kafka、RabbitMQ等&#xff09;&#xff1a; Flu…

css文本属性

css常用文本属性汇总 属性描述color设置文本颜色text-align设置水平对齐方式text-decoration装饰文本text-indent设置缩进line-height设置行间距 设置文本颜色 color属性 属性值&#xff1a; 1.已定义的属性值&#xff0c;如red&#xff0c;green。 2.十六进制&#xff1…

MYSQL08_页的概述、内部结构、文件头、文件尾、最大最小记录、页目录、区段表

文章目录 ①. 页的概述、大小②. 页的内部结构③. 第一部分 - 文件头④. 第一部分 - 文件尾⑤. 第二部分 - 空闲、用户记录、最大最小⑥. 第三部分 - 页目录⑦. 第三部分 - 页面头部⑧. 从数据页角度看B树⑨. 区、段和表、碎片区 ①. 页的概述、大小 ①. 数据库的存储结构&…

计算机服务器中了360后缀勒索病毒怎么办?360后缀勒索病毒解密步骤

网络技术的不断应用与发展&#xff0c;为企业的生产运营提供了极大便利&#xff0c;利用网络可以开展各项工作业务&#xff0c;可以大大提高企业的生产效率&#xff0c;然而&#xff0c;网络是一把双刃剑&#xff0c;在为企业提供便利的同时&#xff0c;也为企业的数据安全带来…

【C 数据结构】单链表

文章目录 【 1. 基本原理 】1.1 链表的节点1.2 头指针、头节点、首元节点 【 2. 链表的创建 】2.0 创建1个空链表&#xff08;仅有头节点&#xff09;2.1 创建单链表&#xff08;头插入法&#xff09;*2.2 创建单链表&#xff08;尾插入法&#xff09; 【 3. 链表插入元素 】【…

下载好了annaconda,但是在创建一个新的Conda虚拟环境报错

文章目录 问题描述&#xff1a;解决方案1.生成一个配置文件 问题总结 问题描述&#xff1a; ProxyError(MaxRetryError(“HTTPSConnectionPool(host‘repo.anaconda.com’, port443): Max retries exceeded with url: /pkgs/pro/win-64/repodata.json.bz2 (Caused by ProxyErr…

【深度学习实战(1)】如何使用argparse模块设置自己的训练参数

一、argparse模块用法 1、argparse是一个python模块&#xff0c;用途是&#xff1a;命令行选项、参数和子命令的解释。 2、argparse库下载&#xff1a;pip install argparse 3、使用步骤&#xff1a; 导入argparse模块&#xff0c;并创建解释器 添加所需参数 解析参数 二、…

Ubuntu去除烦人的顶部【活动】按钮

文章目录 一、需求说明二、打开 extensions 网站三、安装 GNOME Shell 插件四、安装本地连接器五、安装 Hide Activities Button 插件六、最终效果七、卸载本地连接器命令参考 本文所使用的 Ubuntu 系统版本是 Ubuntu 22.04 ! 一、需求说明 使用 Ubuntu 的过程中&#xff0c;屏…

容器镜像进阶

Dockerfile 编写注意事项&#xff1a; 选择合适的基础镜像&#xff0c;没必要追求镜像的绝对大小。 alpine镜像不推荐&#xff0c;尤其是编译型业务&#xff0c;因为alpine镜像内置的musl libc库与标准的glibc不一样。 如果就是想使用alpine镜像&#xff0c;推荐多阶段构建&am…

003Node.js创建第一个web服务

如果用PHP来编写后端代码&#xff0c;需要用Apache或者Nginx的服务器,来处理客户的请求响应。对于Node.js时&#xff0c;不仅实现了应用&#xff0c;同时还实现了整个HTTP服务器. 安装 Node Snippets插件&#xff08;编程自带提示&#xff09; console.log(你好nodejs); //表…

CTF工具下载(1)----随波逐流

为什么要写这个博客喃&#xff0c;因为随波逐流每隔一段时间就会更新&#xff0c;要下载最新版本才能用&#xff0c;但是每次都会有点麻烦&#xff0c;所以写一个博客记录下。 1.进入官网&#xff0c;点击 2.进入城通网盘 3.进入编码工具 4.点击最新版本的随波逐流就下载了&am…

JavaScript进阶6之函数式编程与ES6ESNext规范

函数式编程 柯里化currycurrycompose示例&#xff1a;简化版展开写&#xff1a; debug示例一&#xff1a;示例二&#xff1a; 模板字符串css in js方案 箭头函数问题 生成器 generator应用场景 反射 Reflect 柯里化curry compose是curry的应用 在 lodash/fp underscore ramba …

常用接口测试工具/免费api

一 接口编辑文档 常用的接口文档编写apipost 二 免费接口测试 api 1. thecat 含有&#xff1a; The Cat API - Cat as a Service The Cat API 2. public-apis 进入页面往下拉 三 常用接口测试工具 postman 四 常用接口性能测试工具 Jmeter&#xff0c;loadrunner