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

目录

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.mzph.cn/news/815868.shtml

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

相关文章

全栈的自我修养 ———— react实现滑动验证

实现滑动验证 展示依赖实现不借助create-puzzle借助create-puzzle 展示 依赖 npm install rc-slider-captcha npm install create-puzzleapi地址 实现 不借助create-puzzle 需要准备两张图片一个是核验图形&#xff0c;一个是原图------> 这个方法小编试了后感觉比较麻烦…

【七 (1)FineBI FCP模拟试卷-股票收盘价分析】

目录 文章导航一、字段解释二、需求三、操作步骤1、添加计算字段&#xff08;每月最后一天的收盘价&#xff09;2、绘制折线图 文章导航 【一 简明数据分析进阶路径介绍&#xff08;文章导航&#xff09;】 一、字段解释 Company Name&#xff1a;公司名称 Date&#xff1a;…

【vue】绑定事件 v-on

v-on 简写&#xff1a; clickkeyupkeydownkeyup.wkeyup.ctrl.a <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…

今天掏心窝子!聊聊35岁了程序员何去何从?

今天的内容不聊技术&#xff0c;聊聊轻松的话题&#xff0c;脑子高速转了好几周&#xff0c;停下来思考一下人生…… 不对&#xff0c;关于35岁的问题好像也不轻松&#xff0c;些许有点沉重&#xff0c;反正不是技术&#xff0c;不用高速转动脑细胞了&#xff0c;哈哈。 兄弟…

策略模式【行为模式C++】

1.概述 策略模式是一种行为设计模式&#xff0c; 它能让你定义一系列算法&#xff0c; 并将每种算法分别放入独立的类中&#xff0c; 以使算法的对象能够相互替换。 策略模式通常应用于需要多种算法进行操作的场景&#xff0c;如排序、搜索、数据压缩等。在这些情况下&#x…

Pandas相比Excel的优势是哪些?

熟悉Pandas的同学会知道&#xff0c;Pandas相当于Python中的Excel&#xff0c;都是基于二维表的进行数据处理分析&#xff0c;不同的是&#xff0c;Pandas基于代码操作数据&#xff0c;Excel是图形化的分析工具。 不少人会问Excel比Pandas更简单&#xff0c;为什么还要学习Pan…

wangEditor 测试环境对,但是生产环境无法显示

package.json 文件版本 "wangeditor": "4.3.0"开发环境 new Editor(#${this.id});出来的数据 正式环境 new Editor(#${this.id});出来的数据 原因&#xff1a; vue.config 文件 打包策略的时候 const assetsCDN {css: [https://lf6-cdn-tos.bytecd…

算法1: 素数个数统计

统计n以内的素数个数 素数&#xff1a;只能被1和自身整除的自然数&#xff0c;0和1除外&#xff1b; 举例&#xff1a; 输入&#xff1a;100 输出&#xff1a;25 import java.util.*; class Test1{public static void main(String[] args){int a 100; //输入数字//…

AI来了,Spring还会远吗?(Spring AI初体验)

目录 一、创建项目二、first demo1、application.properties2、ChatController3、结果 三、个人思考 一、创建项目 官方文档的Getting Started 最低要求&#xff1a;JDK17 阿里云的Server URL&#xff08;https://start.aliyun.com/&#xff09;搜不到Spring AI&#xff0c;…

FMix: Enhancing Mixed Sample Data Augmentation 论文阅读

1 Abstract 近年来&#xff0c;混合样本数据增强&#xff08;Mixed Sample Data Augmentation&#xff0c;MSDA&#xff09;受到了越来越多的关注&#xff0c;出现了许多成功的变体&#xff0c;例如MixUp和CutMix。通过研究VAE在原始数据和增强数据上学习到的函数之间的互信息…

缓存与数据库的数据一致性解决方案分析

在现代应用中&#xff0c;缓存技术的使用广泛且至关重要&#xff0c;主要是为了提高数据访问速度和优化系统整体性能。缓存通过在内存或更快速的存储系统中存储经常访问的数据副本&#xff0c;使得数据检索变得迅速&#xff0c;从而避免了每次请求都需要从较慢的主存储&#xf…

中国移动传关停8元保号套餐?或是5G成本带来的压力所致

日前有网友发现希望使用中国移动的保号套餐&#xff0c;却发现已无法办理&#xff0c;媒体对此多有报道&#xff0c;这意味着中国移动的套餐业务发生了重大变动&#xff0c;如此做或许在于5G成本上涨带来的压力促使它不得不提高套餐的门槛。 中国移动已建成最多的5G基站&#x…

服务器主机关机重启告警

提取时间段内系统操作命名&#xff0c;出现系统重启命令&#xff0c;若要出现及时联系确认 重启命令&#xff1a; reboot / init 6 / shutdown -r now&#xff08;现在重启命令&#xff09; 关机命令&#xff1a; init 0 / shutdown -h now&#xff08;关机&#…

uniCloud联表查询方式举例

联查表&#xff1a; 1. 在shema中配置外键&#xff1a; 2.在前端使用&#xff1a; <unicloud-db v-slot:default"{data, loading, error, options}" :options"formData" collection"opendb-news-articles,uni-id-users" //这里这么写 fi…

数据可视化高级技术Echarts(堆叠柱状图)

目录 一.如何实现 二.代码展示 1.stack名称相同&#xff08;直接堆叠&#xff09; 2. stack名称不相同&#xff08;相同的堆叠&#xff0c;不同的新生成一列&#xff09; 一.如何实现 数据堆叠&#xff0c;同个类目轴上系列配置相同的 stack 值可以堆叠放置。即在series中…

【示例】MySQL-4类SQL语言-DDL-DML-DQL-DCL

前言 本文主要讲述MySQL中4中SQL语言的使用及各自特点。 SQL语言总共分四类&#xff1a;DDL、DML、DQL、DCL。 SQL-DDL | Data Definition Language 数据定义语言&#xff1a;用来定义/更改数据库对象&#xff08;数据库、表、字段&#xff09; 用途 | 操作数据库 # 查询所…

SMS垃圾短信识别项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 项目背景 随着数字通信的快速发展&#xff0c;垃圾短信成为了一个普遍而烦人的问题。这些不请自来的消息不仅打扰了我们的日常生活&#xff0c;…

从零全面认识 多线程

目录 1.基本概念 2.创建线程方式 2.1直接建立线程 2.2实现Runnable接口 3.3实现Callable接口 3.4 了解Future接口 Future模式主要角色及其作用 3.5实例化FutureTask类 3.实现线程安全 3.1定义 3.2不安全原因 3.3解决方案 3.4volatile与synchronized区别 3.5Lock与…

创建线程池的例子

public class ExecutorTest {public static void main(String[] args) {//创建线程池的5种方式&#xff1a; // Executors.newFixedThreadPool();//创建固定线程数的线程池 // Executors.newSingleThreadExecutor();//创建单线程的线程池 // Executors.ne…

Geeker-Admin:基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架

Geeker-Admin&#xff1a;基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架 一、引言 随着技术的不断发展&#xff0c;前端开发领域也在不断演变。为了满足现代应用程序的需求&#xff0c;开发人员需要使用最新、最强大的工具和技术。Geeker-Admin正是…