多线程案例-阻塞式队列

news2024/12/27 13:45:12

1.什么是阻塞队列

阻塞队列是一种特殊的队列,在"先进先出"的原则下又引入了"阻塞"功能

阻塞队列能是一种线程安全的数据结构,具有以下特性:

当队列满的时候,继续入队列就会阻塞,直到其它线程从队列中取走元素
当队列空的时候,继续出队列就会阻塞,直到其它队列向队列中插入元素

阻塞式队列的典型应用场景是"生产者消费者模型"

2.生产者消费者模型

生产者消费者模型通过一个容器来解决生产者和消费者的强耦合问题,生产者与消费者之间不直接通讯,通过阻塞队列来通讯,生产数据后放入阻塞队列,消费者可以直接从阻塞队列获取数据

生产者消费者模型能带来两个非常重要的好处:

1.阻塞队列能使生产者和消费者之间解耦
2.阻塞队列起到缓冲的作用,平衡了生产者消费者的处理能力

1.开发中典型的场景:服务器之间的相互调用

当客户端程序向A服务器发起一个请求后,A服务器将请求转发给B服务器处理,然后B服务器处理完成后将结果返回,此时可以视为:A调用了B

这种场景下,AB两个服务器的耦合程度是比较高的!!如果B服务器出现问题,也会引起A的bug,不仅如此,如果A再需要调用C服务器,还需要修改A的代码,非常麻烦..

针对这种场景使用生产者消费者模型,能有效降低耦合

此时,AB之间的耦合就降低很多了,AB都只知道队列的存在,A的代码不与B相关,B的代码也不与A相关,AB任何一方出现问题不会影响到另一方

2.服务器开发中,用户发送的请求的数量是不可控的,如果没有充分的准备并且请求量超过了服务器的承受范围,服务器有可能直接被大量的请求冲垮.例如"秒杀"这种场景,服务器就会同一时刻受到大量请求,这个时候可以把这些请求放到阻塞队列中,让线程慢慢处理,能有效防止服务器被大量的请求冲垮

在Java标准中内置有阻塞队列BlockingQueue,是一个接口,实现类是LinkedBlockingQueue,put方法是入队列,take是出队列.这两个方法具有阻塞特性

下面使用标准库中的阻塞队列实现生产者消费者模型

public class ThreadDemo1 {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        //消费者
        Thread customer = new Thread(()->{
            while(true){
                try {
                    int value = queue.take();
                    System.out.println("消费元素: "+value);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"消费者");
        customer.start();
        //生产者
        Thread producer = new Thread(()->{
            Random random = new Random();
            while(true){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                int num = random.nextInt(1000);
                System.out.println("生产元素: "+num);
                try {
                    queue.put(num);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"生产者");
        producer.start();
        try {
            customer.join();
            producer.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

结果

我们可以发现,生产和消费是成对出现的,程序开始运行,因为在生产者线程中让线程休眠500ms后再执行,此时阻塞队列中为空,而消费者线程要再阻塞队列中使用take方法取元素,就会陷入阻塞状态,等到阻塞队列中有生产者插入元素后才继续执行取元素!!

接下来通过"循环队列"来实现一个阻塞队列

3.阻塞队列实现

实现阻塞队列是要实现一个普通队列然后加上"阻塞功能"

这里我们使用循环队列实现阻塞队列,下面是循环队列的三种状态

我们先写一个普通的循环队列的入队和出队操作

class MyBlockingQueue{
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    //入队列
    public void put(int value){
        if(size == items.length){
            //队列满了.不能插入
            return;
        }
        items[tail] = value;
        tail++;
        //针对tail的处理
        //1)这个写法非常常见
        //tail = (tail+1)%items.length;
        //2)可读性好并且比求余的代码效率高
        if(tail >= items.length){
            tail = 0;
        }
        //插入成功
        size++;

    }
    //出队列
    public Integer take(){
        if(size == 0){
            //队列为空,不能出队
            return null;
        }
        int result = items[head];
        head++;
        if(head >= items.length){
            head = 0;
        }
        size--;
        return result;
    }
}

我们在队列中插入几个元素并取出

 public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();
        queue.put(1);
        queue.put(2);
        queue.put(3);
        queue.put(4);
        int result = 0;
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
        result = queue.take();
        System.out.println(result);
    }

现在我们在普通队列的基础上加上阻塞功能

阻塞功能意味着该队列是要在多线程环境下使用的

多线程环境下要保证线程安全,需要给方法加锁
使用wait()notify()方法添加阻塞功能
当队列为空时和队列满时都需要阻塞
sleep()方法是指定休眠的时间后唤醒,但是我们不能确定指定的时间是多少,需要看程序运行情况

修改后:

public void put(int value){
        synchronized (this){
            if(size == items.length){
                //队列满了.不能插入
                //return;
                //阻塞
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            items[tail] = value;
            tail++;
            //针对tail的处理
            //1)这个写法非常常见
            //tail = (tail+1)%items.length;
            //2)可读性好并且比求余的代码效率高
            if(tail >= items.length){
                tail = 0;
            }
            //插入成功
            size++;
            //唤醒队列为空处的wait()
            this.notify();
        }

    }
    //出队列
    public Integer take(){
        int result = 0;
        synchronized (this){
            if(size == 0){
                //队列为空,不能出队
                //return null;
                //阻塞
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            result = items[head];
            head++;
            if(head >= items.length){
                head = 0;
            }
            size--;
            //
            //唤醒队列满的wait()
            this.notify();
        }
        return result;
    }

上述代码还有个问题

如果notifyAll()了,这里的wait()一定会被唤醒!但是该线程还没有抢占到锁,当锁被这个线程抢占到时,队列的状态可能会是满的,因此我们最好用while循环,然后继续判断队列状态

这样是比较稳妥的方法

我们使用MyBlockingQueue再写一个生产者消费者模型,看是否能达到效果

MyBlockingQueue queue = new MyBlockingQueue();
        Thread customer = new Thread(()->{
            while(true){
                int result = queue.take();
                System.out.println("消费: "+result);
            }
        });
        customer.start();
        Thread producer = new Thread(()->{
            int count = 0;
            while(true){
                System.out.println("生产者: "+count);
                queue.put(count);
                count++;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();

也是成对出现的,只有生产者向队中插入了,消费者才能获取

我们调整代码,让消费者速度降低

再来看结果

生产者生产的数据将循环队列充满后开始阻塞,消费者线程休眠结束后开始获取数据,获取一个后,阻塞队列便出现一个空位置,唤醒put的wait()后,继续生产数据,然后又阻塞等待队列不满.....

至此就用循环队列实现了阻塞队列

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

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

相关文章

内蒙古大学计算机考研893计算机考研真题分享

内蒙古大学计算机学院成立于1997年&#xff0c;软件学院成立于2005年&#xff0c;与计算机学院为一个实体&#xff0c;两个牌子。 目前&#xff0c;学院由计算机科学系、软件工程系、信息管理系和计算中心&#xff08;实验中心&#xff09;组成&#xff0c;设有计算机科学与技…

uniapp开发小程序引入微信快递跟踪(快递100)插件

目录 1.小程序插件接入 2.代码示例 3.页面接收参数 4.常用快递100公司编码表 1.小程序插件接入 微信快递100插件地址&#xff1a;快递100-快递查询&#xff08;免费接入&#xff09; | 微信服务市场 (qq.com) 1&#xff09;进入链接地址 2&#xff09;登陆开发小程序的账…

二叉树18:从中序与后序遍历序列构造二叉树

主要是我自己刷题的一些记录过程。如果有错可以指出哦&#xff0c;大家一起进步。 转载代码随想录 原文链接&#xff1a; 代码随想录 leetcode链接&#xff1a;344. 反转字符串 题目&#xff1a; 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的…

最全Go select底层原理,一文学透高频用法

导语 |在日常开发中&#xff0c;select语句被高频使用。但目前&#xff0c;全网分析select在编译期和运行时的完整底层原理资料&#xff0c;非常匮乏。本文基于Go1.18.1版本的源码&#xff0c;讲解select访问Channel在编译期和运行时的底层原理——select编译器优化用到的src/c…

2022年第十二届APMCM亚太杯1月增赛E题发布

2022年亚洲及太平洋地区建模数学竞赛问题E 有多少颗核弹可以摧毁地球? 1945年8月6日&#xff0c;第二次世界大战接近尾声。为了尽快结束战争&#xff0c;美国在日本广岛投下了名为"小男孩"的下一颗原子弹。这样一颗原子弹炸死了广岛的200000人&#xff0c;广岛的所有…

【数据结构Java版】对象的比较之Comparable与Comparator比较器

目录 一、基本类型的比较 二、对象类型的比较 &#xff08;1&#xff09;对象类型比较出现的问题 &#xff08;2&#xff09;重写基类equals方法 &#xff08;3&#xff09;基于Comparable接口的比较 1.实现Comparable接口&#xff0c;重写compareTo方法 &#xff08;4&a…

Flask框架中常规漏洞防范方法

一、前言 Double Fetch是一种条件竞争类型的漏洞&#xff0c;其主要形成的原因是由于用户态与内核态之间的数据在进行交互时存在时间差&#xff0c;我们在先前的学习中有了解到内核在从用户态中获取数据时会使用函数copy_from_user&#xff0c;而如果要拷贝的数据过于复杂的话…

leetcode | 链表

01 链表知识点 1.1 基础知识 线性表&#xff1a;零个或多个相同类型的数据元素的有限序列&#xff0c;一对一关系&#xff0c;所含数据元素个数n称为线性表的长度。 线性表有两种物理结构(存储结构)&#xff1a;顺序存储、链式存储。 线性表的顺序存储结构&#xff1a;用一段…

优化改进YOLOv5算法之添加SE、CBAM、CA模块(超详细)

目录 1 SENet 1.1 SENet原理 1.2 SENet代码(Pytorch) 1.3 YOLOv5中加入SE模块 1.3.1 common.py配置 1.3.2 yolo.py配置 1.3.3 创建添加RepVGG模块的YOLOv5的yaml配置文件 2 CBAM 2.1 CBAM原理 2.2 CBAM代码(Pytorch) 2.3 YOLOv5中加入CBAM模块 2.3.1 common.py配…

敏捷是一种态度:有了敏捷建模,就有了敏捷需求

目 录01 缘起02 敏捷需求5W1H的思考‍‍‍‍‍‍03 关于敏捷需求体系的一些思考‍‍‍‍‍‍04 写在敏捷需求后的话01缘起对研发效能提升的研究&#xff0c;是近年来各家企业技术部门一直在研究的课题。早期&#xff0c;针对敏捷开发的实践&#xff0c;让大多技术管理者尝到…

114.(leaflet之家)leaflet空间判断-点与圆的空间关系

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>

每个开发人员都应该使用的可扩展和可维护的 React 项目结构

一个好的项目结构可以在理解代码库、灵活性和维护方面对项目的成功产生巨大影响。结构和维护不当的项目很快就会变成一团糟和可怕的遗产&#xff0c;没有人愿意与之共事。我现在将向您展示我在项目中经常使用的结构&#xff0c;并解释其背后的原因。这种结构应该是大规模应用程…

开源代码 | FMCW-MIMO雷达仿真MATLAB

本文编辑&#xff1a;调皮哥的小助理 本程序来源&#xff1a;https://github.com/ekurtgl/FMCW-MIMO-Radar-Simulation&#xff0c;作者是阿拉巴马大学博士生艾库特格尔&#xff0c;研究方向主要是雷达信号处理人类活动识别以及雷达数据的机器学习应用&#xff0c;这份比较新的…

STM32MP157驱动开发——4G通信模块驱动

STM32MP157驱动开发——4G通信模块驱动一、简介二、驱动开发1.高新兴 ME3630 驱动开发驱动修改添加 ECM 支持程序配置 Linux 内核ppp拨号功能测试ECM 联网测试ME3630 4G 模块 GNSS 定位测试2.移远EC20 4G驱动开发驱动修改配置 Linux 内核EC20 ppp 拨号上网移远 GobiNET 驱动移植…

go语言学习(一):Mac环境安装及初始化

​ ​为什么要学习go语言? 1、简洁&#xff0c;快速&#xff0c;安全&#xff1b; ​ ​2、并行&#xff0c;有趣&#xff0c;开源​&#xff1b; 3、内存管理&#xff0c;数据安全&#xff0c;编译迅速 首先&#xff0c;去官网&#xff1a;https://golang.google.cn/dl…

面向对象3(多态、多态调用成员函数的特点、多态的优势和弊端及改进、包、final、权限修饰符、代码块、抽象方法和抽象类、接口、内部类)

1、多态 2、多态调用成员函数的特点 示例如下&#xff1a; 理解&#xff1a; 因为是Animal类型的&#xff0c;所以在输出name时会在父类继承下来的变量里面找 &#xff0c;没有就报错。而一般是先找自己再找父类继承下来的。 3、多态的优势和弊端及改进 优势&#xff1a; 弊端…

Arduino——野火GPS模块

GPS模块 文章目录GPS模块前言一、Arduino代码前言 手上还有一个GPS&#xff0c;用arduino做模块很方便&#xff0c;打算和短信模块结合&#xff0c;短信模块上次已经使用完成了&#xff0c;这次学习一下GPS模块 看模块很容易知道&#xff0c;这个模块用的是串口通信&#xff…

基于STM32智能家居控制系统软件设计及实现

1.1 系统流程图 智能家居控制系统的软件设计主要使用Keil uVision5进行STM32主烧录程序的编写&#xff0c;主程序完成的功能主要为接收并判断语音识别模块传过来的信息&#xff0c;然后根据满足条件的不同进行对应的操作。例如&#xff0c;当语音模块传过来的信息为“打开电视…

top详解--查看cpu及内存使用情况

top详解--查看cpu及内存使用情况 参考链接:http://t.zoukankan.com/guoyu1-p-12237660.html 一、top命令 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 运行 top 命令后,CPU 使用状态会以全屏的方式显示,…

基于Java+SpringBoot+vue+element实现婚纱摄影网系统

基于JavaSpringBootvueelement实现婚纱摄影网系统 &#x1f345; 作者主页 超级帅帅吴 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 文章目录基于JavaSpringBootvueelement实现婚纱摄影网系统前言介绍&…