JavaEE 第9节 阻塞队列详解

news2024/9/21 11:10:36

一、概念

阻塞队列是在普通队列(先进先出的数据结构)的基础上增加了阻塞属性的特殊队列

       1)当阻塞队列空的时候,如果继续出队元素会进入阻塞状态,直到其他线程入队元素。

       2)当阻塞队列满的时候,如果继续入队元素也会进入阻塞状态,直到其他线程出元素。

阻塞队列队列的一个典型应用场景就是“生产者消费者模型”。


二、生产者消费者模型

生产者消费者模型共有三大模块:

1、生产者 

2、缓冲区(一般指的就是阻塞队列)

3、消费者

这里面生产者产生的数据需要传递给消费者去处理,但是生产者与消费者之间不是直接通信的而是借助中间模块“缓冲区”,也就是阻塞队列来完成

生产者把产生的数据都抛给阻塞队列,消费者获取数据也全在阻塞队列里面找。

举一个包饺子的形象例子:

如果左孩子,擀饺子皮速度比右孩子包饺子速度快,那么盖帘(阻塞队列)很可能就满了,满了的话,左孩子就会停下来玩会儿手机(进入阻塞状态),等右孩子(消费者)从盖帘(阻塞队列)拿走一个饺子皮,然后左孩子在取擀饺子皮。

阻塞队列优点

1、平衡生产者和消费者的处理能力,保护下游服务器

在上述包饺子的例子中,当生产者产生的产生的数据比较多的时候(擀面擀的包饺子的人快很多),如果没有阻塞队列,数据直接全部都给到消费者那么消费者处理数据的压力就非常大,甚至是服务器崩溃。但是有了阻塞队列作为缓冲区,就可以避免这种事情的发生。

有没有这样的疑问,数据量的激增是从生产者开始,然后是阻塞队列,最后是消费者的,为什么生产者、阻塞的队列不会先挂?


这实际上是与生产者和消费者的处理数据的规模量有关。

生产者实际上就是一个请求接收和转移窗口,它对数据的处理其实非常少(阻塞队列同理),相当于就是负责传输数据的,但是消费者不仅要接收数据,还有对数据进行计算处理,处理每个数据的任务量远远大于生产者、阻塞队列。

2、生产者和消费者之间解耦合

通过阻塞队列,生产者与消费者之间的联系实际上就变少了,他们的代码这和缓冲区有关,当其中的一个模块挂了或者需要增加消费者\生产者,对其他模块的影响或者修改比较少,利于代码的维护。

缺点

缺点也很明显,多增加了一个缓冲区模块,那么程序响应速度必然会降低!


三、JAVA标准库中阻塞队列的使用方式

常用接口认识

调用方法的选择

在BlockingQueue中,与普通队列一样带有poll和offer两个方法,但是一般都不用这两个方法,因为它们不带有阻塞效果

与之对应的带来了两个新的方法takeput。这两个方法是带有阻塞效果的:

public class Queue {
    public static void main(String[] args) throws InterruptedException {

        BlockingQueue<Integer> blockingQueue=new ArrayBlockingQueue<Integer>(3);
        blockingQueue.put(1);
        blockingQueue.put(1);
        blockingQueue.put(1);
        blockingQueue.put(1);//在插入一个主线程就会WAITING

    }
}

接下来用put和take模拟一个生产者


消费者模型:

public class Queue {
    public static void main(String[] args) throws InterruptedException {

        BlockingQueue<Integer> blockingQueue=new ArrayBlockingQueue<Integer>(10);

        Thread thread1=new Thread(()->{
            int i=0;
            while(true){
                i++;
                System.out.println("生产一个元素:"+i);
                try {
                    blockingQueue.put(i);
                } catch (InterruptedException e) {//调用put或者take都需要处理中断异常
                    e.printStackTrace();
                }
            }
        });

        Thread thread2=new Thread(()->{
           while(true){
               try {
                   int i=blockingQueue.take();
                   Thread.sleep(1000);//模拟消费者处理速度慢的情况
                   System.out.println("消费一个元素:"+i);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }

        });
        thread1.start();
        thread2.start();

    }
}

执行了大约1分钟后的结果,程序稳定执行:

虽然生产速度要快于消费速度,但是没有出现程序崩溃的情况,最终程序会稳定执行下去,虽然可能会慢一点,这就是阻塞队列的优点和缺点的体现。


四、简单的阻塞队列实现

这里就不用泛型了,直接用一个String类的队列来实现,简单了解它的原理。

底层用数组,使用循环队列(含useSize)创建一个普通队列:

class MyBlockingQueue {
    //用数组实现
    String[] data = null;

    //分别表示头、尾、队列长度
    int head = 0, tail = 0, useSize = 0;
    /**指向有效元素的指针是左闭右开的,[head,tail)
     * tail端put进
     * head端take出*/
    
    

    //构造方法,设置最大容量
    public MyBlockingQueue(int capacity) {
        this.data = new String[capacity];
    }
    
    //插入方法
    public void put(String s){
        //如果队列满了,不做处理
        if(useSize==data.length)return;
        
        data[tail]=s;//直接赋值,因为tail区间是开的
        tail++;
        if(tail>=data.length)tail=0;
        useSize++;//记得长度加一
    }
    
    //取出方法
    public String take(){
        
        //没有就取不出来
        if(useSize==0)return null;
        
        String ret=data[head];
        head++;
        if(head>=data.length)head=0;
        useSize--;
        return ret;
    }

}

在这个普通队列的基础上,我们要实现阻塞的效果,在线程中,所以还需要对put和take方法进行改造,对方法进行加锁,并使用wait和notify方法来相互之间通信:

class MyBlockingQueue {
    //用数组实现
    private String[] data = null;

    //分别表示头、尾、队列长度
   private volatile int head = 0, tail = 0, useSize = 0;//加volatile没有坏处,虽然出现内存可加性优化概率很低(while循环执行速度并不快)
    /**指向有效元素的指针是左闭右开的,[head,tail)
     * tail端put进
     * head端take出*/



    //构造方法,设置最大容量
    public MyBlockingQueue(int capacity) {
        this.data = new String[capacity];
    }

    //插入方法
    public void put(String s) throws InterruptedException {
//        //如果队列满了,不做处理
//        if(useSize==data.length)return;

     synchronized(this){/**this指的是当前类,创建的一个实例,其他实例对象也可以,只要对象是匹配的*/
         if(useSize==data.length){
             this.wait();//让当前线程先睡眠,等其他线程take元素了,然后再唤醒它
         }
         data[tail]=s;//直接赋值,因为tail区间是开的
         tail++;
         if(tail>=data.length)tail=0;
         useSize++;//记得长度加一
         this.notify();//put完了,反过来通知take
     }
    }

    //取出方法
    public String take() throws InterruptedException {

//        //没有就取不出来
//        if(useSize==0)return null;


        synchronized(this){
            if(useSize==0){
                this.wait();
            }
            String ret=data[head];
            head++;
            if(head>=data.length)head=0;
            useSize--;
            this.notify();//take完了,通知一下put
            return ret;
        }
    }
}

这个代码其实还有一个小瑕疵,不知道大家有没有发现。

wait方法除了notify方法可以把他唤醒,之前学过的interrupt方法实际上也会把调用了wait方法的线程唤醒然后终止该线程!

倘若一个不小心在put或者take的时候,调用了interrupt方法,那么即使if条件不成立,程序还会继续往下执行(因为Java中断线程是柔和的方式,没有用try catch具体处理中断后的情况,那么程序就会继续往下执行),这就会造成不可预期的后果:

解决这个问题也很简单,直接把if换成while循环即可,即使中断了循环在进入睡眠即可:

class MyBlockingQueue {
    //用数组实现
    private String[] data = null;

    //分别表示头、尾、队列长度
    private volatile int head = 0, tail = 0, useSize = 0;
    /**指向有效元素的指针是左闭右开的,[head,tail)
     * tail端put进
     * head端take出*/



    //构造方法,设置最大容量
    public MyBlockingQueue(int capacity) {
        this.data = new String[capacity];
    }

    //插入方法
    public void put(String s) throws InterruptedException {
//        //如果队列满了,不做处理
//        if(useSize==data.length)return;

     synchronized(this){/**this指的是当前类,创建的一个实例,其他实例对象也可以,只要对象是匹配的*/
         while(useSize==data.length){
             this.wait();//让当前线程先睡眠,等其他线程take元素了,然后再唤醒它
         }
         data[tail]=s;//直接赋值,因为tail区间是开的
         tail++;
         if(tail>=data.length)tail=0;
         useSize++;//记得长度加一
         this.notify();//put完了,反过来通知take
     }
    }

    //取出方法
    public String take() throws InterruptedException {

//        //没有就取不出来
//        if(useSize==0)return null;


        synchronized(this){
            while(useSize==0){
                this.wait();
            }

            String ret=data[head];
            head++;
            if(head>=data.length)head=0;
            useSize--;
            this.notify();//take完了,通知一下put
            return ret;
        }
    }
}

注意:

以上代码成立的条件是put和take不可能同时调用进入WAITING状态,因为useSize不可能同时满足useSize==0&&useSize==data.length。

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

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

相关文章

瑞萨电子并购Altium 引领行业创新与发展

公开资料显示&#xff0c;2023 年 6 月&#xff0c;瑞萨电子曾宣布在 Altium 的 Altium 365 云平台上实现了所有 PCB 设计的标准化开发。瑞萨电子一直与 Altium 合作&#xff0c;将其所有产品的 ECAD 库发布到 Altium Public Vault。借助 Altium365 上的制造商零件搜索等功能&a…

ROW_NUMBER(), RANK(), DENSE_RANK() SQL排序函数图文详解

ROW_NUMBER(), RANK(), DENSE_RANK() ROW_NUMBER(): 为结果集中的每一行分配唯一的连续编号。即使有重复的值&#xff0c;ROW_NUMBER() 也会为它们分配不同的序号。 SELECT column_name, ROW_NUMBER() OVER (ORDER BY column_name) AS row_num FROM table_name;2. RANK(): 对结…

高可用集群keepalived从部署到实战一篇解决

目录 一.高可用集群 1.1 集群类型 1.2 系统可用性 1.3 系统故障 1.4 实现高可用 1.5.VRRP&#xff1a; 1.5.1 VRRP 相关术语 1.5.2 VRRP 相关技术 二.Keepalived 部署 2.1 keepalived 简介 2.2keepalived架构 2.3 Keepalived 环境准备 2.4 Keepalived 相关文件 2.…

java实现七牛云内容审核功能,文本、图片和视频的内容审核(鉴黄、鉴暴恐、敏感人物)

目录 1、七牛云内容审核介绍 2、查看内容审核官方文档 2.1、文本内容审核 2.1.1、文本内容审核的请求示例 2.1.2、文本内容审核的返回示例 2.2、图片内容审核 2.2.1、请求参数 2.2.2、返回参数 2.3、视频内容审核 3、代码实现 3.1、前期代码准备 3.2、文本内容审核…

基于Spring + Vue的旅游景区项目+源代码+文档说明

文章目录 源代码下载地址项目介绍项目功能界面预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 基于Spring Vue的旅游景区项目 项目功能 民宿管理员&#xff1a;订单数量统计&#xff0c;订单交易额统计&#xff0c;客房统计饼图&#xff0c;酒店…

【STM32嵌入式系统设计与开发拓展】——14_定时器之输入捕获

参考哔站&#xff1a;链接: 铁头山羊 一、微控制器的高级定时与控制功能集合 1、时基单元 2、输入捕获 3、输出比较 4、从模式控制器 5、高级定时器的输出控制 二、问题集合 1、什么是定时器 定时器是一种专门负责定时功能的片上外设GPI0AFI0EXTIUSART RCC I2C) 2、定时器…

飞越现实:3D可视化引领飞行体验新纪元

在浩瀚的蓝天之下&#xff0c;飞机以优雅的姿态划破长空&#xff0c;每一次起降、每一次盘旋&#xff0c;都是对速度与科技的完美诠释。而今&#xff0c;随着科技的飞速发展&#xff0c;我们不再仅仅满足于仰望天际的壮丽景象&#xff0c;而是能够借助先进的3D可视化技术&#…

JavaEE 第10节 线程池(Thread Pool)介绍

目录 一、线程池是什么 二、为什么线程池中取线程会比直接向操作系统申请来的高效&#xff1f; 三、JAVA标准库中的线程池 &#xff08;1&#xff09;类&#xff1a;ThreadPoolExecutor 1、int corePoolSize与int maximumPoolSize 2、long keepAlive和TimeUnit unit 3、Blo…

MySQL第6讲--DQL(数据查询语言)的基本操作之基本和条件查询

文章目录 前言DQL(数据查询语言)基本操作查询操作基本查询示例1&#xff1a;查询表格的name&#xff0c;age&#xff0c;并返回&#xff1b;示例2&#xff1a;查询表格中的所有字段&#xff1b;示例3&#xff1a;查询所有员工的工号并返回&#xff0c;起别名&#xff1b;示例4&…

人工智能在新药研发领域中发挥着至关重要的作用

本综述主要介绍机器学习和深度学习方法在药物发现领域的应用进展以及相关企业。 声明&#xff1a;本文为火石创造原创文章&#xff0c;欢迎个人转发分享&#xff0c;网站、公众号等转载需经授权。 本文选自《药学进展》2021年第7期&#xff0c;作者黄芳 1&#xff0c;杨红飞 1…

武汉流星汇聚:中国卖家亚马逊显威,供应链创新引领全球电商潮

在全球电商的浩瀚星空中&#xff0c;亚马逊无疑是最耀眼的那颗星&#xff0c;其庞大的用户基础、广泛的销售网络以及强大的品牌影响力&#xff0c;为无数商家提供了通往成功的快车道。而在这片充满机遇的蓝海中&#xff0c;中国卖家以其独特的优势&#xff0c;正逐渐成为一股不…

钢铁百科:SA572Gr60应用领域、SA572Gr60热处理状态、SA572Gr60常用规格

一、SA572Gr60材质与执行标准 SA572Gr60钢板是一种美标高强度低合金铌-钒结构钢板&#xff0c;执行标准为ASTM A572/A572M。此外&#xff0c;该钢板也符合ASME标准SA-572/SA-572M。 二、SA572Gr60化学成分 SA572Gr60钢板的主要化学成分包括&#xff1a; 碳C&#xff1a;0.16-…

haproxy高级功能及配置

目录 1.基于cookie的会话保持&#xff1a; 2.HAProxy状态页&#xff1a; 3.IP透传 1.基于cookie的会话保持&#xff1a; cookie value&#xff1a;为当前server指定cookie值&#xff0c;实现基于cookie的会话黏性&#xff0c;相对于基于 source 地址 hash 调度算法对客户端…

msgqueue.hpp队列模块

一.MsgQueue相关类介绍 二.MsgQueue类的实现 成员变量 MsgQueue 结构体用于描述一个消息队列的基本属性。 std::string _name; // 队列名称 bool _durable; // 队列是否持久化 bool _exclusive; // 队列是否独占 bool _auto_del; // 队列是否自动删除 google::pro…

版本控制基础理论

一、本地版本控制 在本地记录文件每次的更新&#xff0c;可以对每个版本做一个快照&#xff0c;或是记录补丁文件&#xff0c;适合个人使用&#xff0c;如RCS. 二、集中式版本控制&#xff08;代表SVN&#xff09; 所有的版本数据都保存在服务器上&#xff0c;协同开发者从…

在HTML中固定表格表头的简单方法

在HTML中&#xff0c;表格元素自身无法提供滚动以及固定表头的配置。借助第三方工具&#xff08;如jQuery的表头固定插件&#xff09;或者结合JavaScrip&#xff0c;是可以实现表格的表头固定的&#xff0c;除此之外&#xff0c;本文还想讨论一种更简单的方式来实现。 从思路上…

【初阶数据结构】详解顺序表(下)(顺序表的代码实现)

文章目录 前言1. 项目文件的配置1.1 顺序表的项目的文件配置(仅供参考) 2. 顺序表的代码实现2.1 SeqList.h&#xff1a;2.2 SeqList.c:2.2.1 顺序表初始化的代码实现&#xff1a;2.2.2 顺序表销毁的代码实现&#xff1a;2.2.3 顺序表尾插数据的代码实现&#xff1a;2.2.4 顺序表…

【国赛必看!】数学建模python基础教学及常用算法代码包分享

一、内容介绍 Python在各个编程语言中比较适合新手学习&#xff0c;Python解释器易于扩展&#xff0c;可以使用C、C或其他可以通过C调用的语言扩展新的功能和数据类型。 Python也可用于可定制化软件中的扩展程序语言。Python丰富的标准库&#xff0c;提供了适用于各个主要系统…

opencv-python图像增强六:低光照增强

文章目录 一&#xff1a;简介二、低光照图像增强方案&#xff1a;三、算法实现步骤3.1 CLAHE直方图均衡化&#xff1a;3.2 伽马变换&#xff1a;3.3 对亮度通道做伽马变换 四&#xff1a;整体代码实现五&#xff0c;效果&#xff1a; 一&#xff1a;简介 低光照图像增强是一种…