阻塞式队列

news2024/11/8 11:22:55

在这里插入图片描述

文章目录

  • 一、阻塞队列
    • 阻塞队列的概念
    • 阻塞队列相关类和方法
    • 生产者消费者模型
  • 二、自定义实现阻塞队列
    • 自定义实现循环队列
    • 自定义实现阻塞队列
    • 生产者消费者模型体验

一、阻塞队列

阻塞队列的概念

队列我们并不默认,一提起队列,我们立马就能想到 "先进先出"的特性。
今天我们就来学习一下特殊的队列: 阻塞队列,它具有下面两个特性:

  1. 如果队列为空,执行出队列操作时,就会阻塞,直到另一个线程往队列里添加元素(队列不为空时)
  2. 如果队列为满,执行入队列操作时,就会阻塞,直到另一个线程从队列里取走元素
    (队列不为满时)

阻塞队列相关类和方法

在这里插入图片描述
我们的JUC下为我们提供了一个泛型接口BlockingQueue.
在这里插入图片描述
同时也提供了一下具体的实现类:

ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。

同时也为我们提供了一些操作阻塞队列的方法。

方法作用
void put()带有阻塞特性的入队操作
E take()带有阻塞特性的出队特性
boolean contains(Object o)判断阻塞队列是否包含某元素

我们来简单的使用一下阻塞队列

public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        queue.put(1);
        queue.put(2);
        Integer take = queue.take();
        System.out.println("取出阻塞队列中的元素: " + take);
        take = queue.take();
        System.out.println("取出阻塞队列中的元素: " + take);
        take = queue.take();
        System.out.println("取出阻塞队列中的元素: " + take);
    }

在这里插入图片描述
我们可以发现我们阻塞队列只放入了2个元素,当我们取元素时,取第三个时,因为阻塞队列没有元素,所以一直在阻塞等待。

生产者消费者模型

为什么会有生产者消费者模型?
在我们多线程编程种,为了解决生产者(生产数据的线程)和消费者(消费数据的线程)之间的执行速度的差异和解决生产者和消费者之间的强耦合关系。
在这里插入图片描述
通过在生产者和消费者之间增加一个队列来避免生产者和消费者之间的直接通信,生产者将数据直接添加到队列中,消费者直接从队列中取数据处理。
那么这样的模型有什么好处呢?

1.降低生产者和消费者之间的耦合度
2.增加一个阻塞队列,平衡生产者和消费者之间的处理能力
在这里插入图片描述
比如我们的双11,12的购物狂欢节,某一时刻的秒杀活动,我们的服务器A某一时刻会向服务器B发送巨量的强求数据,但是我们的服务器处理速度有限,一次性招架不住这么多请求,有可能服务器之间就崩了。
在这里插入图片描述
我们使用生产者消费者模型时,就不会受到影响,即使服务器的请求暴涨,但是他发送到阻塞队列中,我们的服务器B按照自己的处理速度去阻塞队列中取元素,所以不会产生实质性的影响。

public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        Thread customer = new Thread(() -> {
           while(true) {
               try {
                   int val = queue.take();
                   System.out.println("消费者消费了: " + val);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        customer.start();
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    queue.put(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();
    }

在这里插入图片描述

二、自定义实现阻塞队列

我们的阻塞队列可以由链表实现,也可以由数组实现,由于链表实现比较简单,尾插头删或者头插尾删即可,这里我们重点介绍数组实现阻塞队列,大家也可以先去学习一下队列这篇文章:java详解队列

自定义实现循环队列

在这里插入图片描述
在实现循环队列时,我们主要面临的问题是,什么情况下队列为空,什么情况下队列为满,在判断满时:我们有两种方案,定义一个size变量,如果等于0为空,等于队列容量为满,这种过于简单,我们采用浪费一个空间的办法,如果head == tail队列为空,如果tail的下一个位置为head为满。

public class MyCircularQueue {
    //定义队列,默认大小50
    private int[] num = new int[50];
    //定义队头指针
    private int head;
    //定义队尾指针
    private int tail;
    //记录队列元素
    private int size;


    //入队
    public void put(int val) {
        if(size == num.length) {
            //队列为满
            return;
        }
        num[tail] = val;
        tail++;
        // 等价tail = tail % num.length
        if(tail >= num.length) {
            tail = 0; //这样的写法,可读性较高
        }
        size++;
    }
    
    //出队
    public Integer take() {
        if(size == 0) {
            //队列为空
            return null; 
        }
        int ret = num[head];
        head++;
        //等价 head = head % num.length
        if(head >= num.length) {
            head = 0;
        }
        size--;
        return ret;
     }
}

自定义实现阻塞队列

因为我们的阻塞队列基本都是在多线程情况下使用的,我们的put和take方法都涉及了多线程的读写操作,所以我们加锁。

public class MyBlockingQueue {
    //定义队列,默认大小50
    private int[] num = new int[50];
    //定义队头指针
    private int head;
    //定义队尾指针
    private int tail;
    //记录队列元素
    private int size;


    //入队
    public void put(int val) {
        synchronized (this) {
            if(size == num.length) {
                //队列为满
                return;
            }
            num[tail] = val;
            tail++;
            // 等价tail = tail % num.length
            if(tail >= num.length) {
                tail = 0; //这样的写法,可读性较高
            }
            size++;
        }
    }

    //出队
    public Integer take() {
        synchronized (this) {
            if(size == 0) {
                //队列为空
                return null;
            }
            int ret = num[head];
            head++;
            //等价 head = head % num.length
            if(head >= num.length) {
                head = 0;
            }
            size--;
            return ret;
        }
    }
}

阻塞队列,其最重要的功能就是阻塞,既然我们要实现阻塞功能,我们这里的业务场景需要,主要使用wait 和 notify实现阻塞等待,以及唤醒。我们需要在两处阻塞

  1. 当我们take时,队列为空,我们需要wait方法使其阻塞,直到有元素入队列时,在notify将其唤醒。
  2. 当我们put时,队列为满,我们需要wait方法使其阻塞,直到有元素出队列时,在notify将其唤醒。
public class MyBlockingQueue {
    //定义队列,默认大小50
    private int[] num = new int[50];
    //定义队头指针
    private int head;
    //定义队尾指针
    private int tail;
    //记录队列元素
    private int size;


    //入队
    public void put(int val) throws InterruptedException {
        synchronized (this) {
            if(size == num.length) {
                //队列为满,阻塞等待
                this.wait();
            }
            num[tail] = val;
            tail++;
            // 等价tail = tail % num.length
            if(tail >= num.length) {
                tail = 0; //这样的写法,可读性较高
            }
            size++;
            //唤醒take方法中因队列为空阻塞的线程
            this.notify();
        }
    }

    //出队
    public Integer take() throws InterruptedException {
        synchronized (this) {
            if(size == 0) {
                //队列为空,阻塞等待
                this.wait();
            }
            int ret = num[head];
            head++;
            //等价 head = head % num.length
            if(head >= num.length) {
                head = 0;
            }
            size--;
            //唤醒put方法中因队列为满阻塞的线程
            this.notify();
            return ret;
        }
    }
}

在这里插入图片描述
上述代码,我们已经基本实现了阻塞队列的功能,但是有一点美中不足的地方,我们的wait被唤醒之后,一定能确保if的条件一定是不满足的吗?
虽然这里可能是肯定的,但我们是一个逻辑严密的程序员,我们在被唤醒的时候在去判断一下是否满足更为妥善。
在这里插入图片描述
我们wait的源码中,也是这样建议的,所以我们修改完善一下。

//入队
    public void put(int val) throws InterruptedException {
        synchronized (this) {
            while(size == num.length) {
                //队列为满,阻塞等待
                this.wait();
            }
            num[tail] = val;
            tail++;
            // 等价tail = tail % num.length
            if(tail >= num.length) {
                tail = 0; //这样的写法,可读性较高
            }
            size++;
            //唤醒take方法中因队列为空阻塞的线程
            this.notify();
        }
    }

    //出队
    public Integer take() throws InterruptedException {
        synchronized (this) {
            while(size == 0) {
                //队列为空,阻塞等待
                this.wait();
            }
            int ret = num[head];
            head++;
            //等价 head = head % num.length
            if(head >= num.length) {
                head = 0;
            }
            size--;
            //唤醒put方法中因队列为满阻塞的线程
            this.notify();
            return ret;
        }
    }

在这里插入图片描述
在这里插入图片描述

生产者消费者模型体验

我们用我们自定义实现的阻塞队列体验一下生产者消费者模型。

public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();
        Thread producer = new Thread(() -> {
           int count = 0;
            while (true) {
                try {
                    queue.put(count);
                    System.out.println("生产了" + count);
                    Thread.sleep(50);
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
        Thread customer = new Thread(() -> {
           while (true) {
               try {
                   int val = queue.take();
                   System.out.println("消费了: " + val);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        customer.start();
    }

在这里插入图片描述
这样就实现了一个源源不断的生产者消费者模型。这些内容大家还需要多加练习。

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

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

相关文章

一条 update 语句的执行过程

1.一条 update 语句的执行流程 一条更新语句&#xff0c;其实是增&#xff0c;删&#xff0c;查的综合体&#xff0c;查询语句需要经过的流程&#xff0c;更新语句全部需要执行一次&#xff0c;因为更新之前必须要先拿到&#xff08;查询&#xff09;需要更新的数据。 Buffer…

低代码平台组件间通信方案复盘

背景介绍3年前我开发了一款零代码搭建平台 H5-Dooring, 主要目的是想用更低的成本, 更快的效率, 上线 web 页面(其实是不想写重复的代码了,写麻了). 好在陆陆续续折腾了3年, 目前已经可以满足基本的页面设计和搭建能力, 并能快速上线页面.之前也在社区分享了很多低代码和零代码…

高、低成本MEMS惯导系统姿态、位置、速度更新算法的对比

高、低成本MEMS惯导系统姿态、位置、速度更新算法的对比一、高成本MEMS惯导系统姿态、位置、速度更新算法1、速度更新2、位置更新3、姿态更新4、程序仿真及实验结果4.1 主函数4.2 子函数4.3 实验结果一、低成本MEMS惯导系统姿态、位置、速度更新算法1、速度更新2、位置更新3、姿…

乾元浩在创业板IPO终止:主要生产禽用疫苗产品,中农大是股东

2023年1月4日&#xff0c;深圳证券交易所披露的信息显示&#xff0c;乾元浩生物股份有限公司&#xff08;下称“乾元浩”&#xff09;提交了撤回上市申请文件的申请&#xff0c;保荐人中信证券也撤回对该公司的保荐。因此&#xff0c;深交所终止了乾元浩首次公开发行股票并在创…

程序员述职报告

程序员述职报告笔者能力有限&#xff0c;仅供参考。做研发的小伙伴&#xff0c;不太擅长于写PPT&#xff0c;对于更高一级别的领导&#xff0c;可能只有年终述职的时候才能全面的了解你的工作。所以需要我们在PPT中表达自我价值&#xff0c;突出角色职责。让领导更清楚的认识你…

访问数据库超时问题排障

1 排障过程 系统从圣诞节那天晚上开始&#xff0c;每天晚上固定十点多到十一点多这个时段&#xff0c;大概瘫痪1h左右&#xff0c;过这时段系统自动恢复。系统瘫痪时的现象就是&#xff0c;网页和App都打不开&#xff0c;请求超时。系统架构&#xff1a; 整个系统托管在公有云…

图解数据结构:盘点链表与栈和队列的那些血缘(单双链表模拟实现栈和队列)

写在前面 Hello&#xff0c;各位盆友们&#xff0c;我是黄小黄。关于前一段时间为什么拖更这件事&#xff0c;这里给大家说一句抱歉。笔者前段时间忙于ddl和一些比赛相关的事件&#xff0c;当然还有些隐藏任务&#xff0c;所以博文更新就放缓了。  这里还需要做一下对以后博文…

计算机原理一_计算机的组成、进程与线程

目录儿一、计算机组成二、进程与线程2.1 线程的切换2.2 CPU的并发控制2.2.1 关中断2.2.2 缓存一致性协议2.2.2.1 缓存Cache2.2.2.2 缓存行Cache Line2.2.2.3 缓存一致性拓展&#xff1a;超线程2.2.3 内存屏障2.2.3.1 CPU的乱序执行拓展1&#xff1a;java 的 this 溢出问题拓展2…

Linux(一):Linux基本结构

一、Linux系统划分 linux系统分为用户区、内核区 1.1分区目标 保护数据和硬件安全&#xff0c;对系统进行分区也就是进程分区&#xff0c;当处于用户态&#xff0c;只能访问用户区&#xff0c;用户无法修改内核&#xff0c;保证硬件安全&#xff0c;操作系统不易损坏&#…

【Qt】QMainWindow应用程序窗口类简单介绍

QMainWindow介绍 QMainWindow是一个为用户提供主窗口程序的类&#xff0c;是许多应用程序的基础&#xff0c;包含的组件有&#xff1a; 菜单栏QMenuBar&#xff0c;一个主窗口最多只能有一个菜单栏&#xff1b;包含一个下拉菜单项的列表&#xff0c;这些菜单项由QAction动作类…

【git版本控制】| git版本控制操作命令(全)

文章目录一、简介二、工作模式1 集中式&#xff08;CVS、SVN&#xff09;2 分布式Git三、Git1 工作模式2 git工作流程3 工作区和版本库4 注意事项5 基本操作5.1 创建本地版本库5.2 初始化本地版本库5.3 .git目录的作用5.4 创建用户5.5 其他操作6 git分支7 常见警告8 免密登录9 …

interface接口--GO面向对象编程思想

一、interface接口 interface 是GO语言的基础特性之一。可以理解为一种类型的规范或者约定。它跟java&#xff0c;C# 不太一样&#xff0c;不需要显示说明实现了某个接口&#xff0c;它没有继承或子类或“implements”关键字&#xff0c;只是通过约定的形式&#xff0c;隐式的…

【C语言进阶】自定义类型:结构体,枚举,联合体

目录 1、结构体的声明 1.1 结构体基础知识 1.2 结构体的声明 1.3 特殊的声明 1.4 结构体的自引用 1.5 结构体变量的定义和初始化 1.6 结构体内存对齐 ​编辑1.7 修改默认对齐数 1.8 结构体传参 2. 位段 2.1 什么是位段 2.2 位段的内存分配 2.3 位段的跨平台问…

【owt-server】代码结构及新增一个agent

owt server 官方 5.0 仓库:代码结构 manage console manage api portal sip portal 与agent 并列 agent又有很多种类。 启动脚本 启动一个新的agent 比如streaming-agent streaming-agent )cd ${OWT_HOME}/s

分布式id

分布式id一 什么是分布式系统唯一ID二 分布式系统唯一ID的特点三 分布式系统唯一ID的实现方案3.1 基于UUID3.2 基于数据库自增id3.3 基于数据库集群模式3.4 基于Redis模式3.5 基于雪花算法&#xff08;Snowflake&#xff09;模式3.6 百度&#xff08;uid-generator&#xff09;…

Python爬虫数据到sqlite实例

参考链接:https://blog.csdn.net/qq_45775027/article/details/115319253最近需要使用到爬虫数据库&#xff0c;原文中作者有些没补齐&#xff0c;略作修改之后跑通了。主要修改&#xff1a;1.调整了数据获取的正则表达式&#xff1b;2. 改了一下数据库的table名和定义名字&…

基于Java+SpringBoot+vue+element实现前后端分离牙科诊所管理系统详细设计

基于JavaSpringBootvueelement实现前后端分离牙科诊所管理系统详细设计 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目…

【Linux】虚拟地址空间 --- 虚拟地址、空间布局、内存描述符、写时拷贝、页表…

该吃吃&#xff0c;该喝喝&#xff0c;遇事儿别往心上隔&#x1f60e; 文章目录一、虚拟地址空间1.虚拟地址的引出&#xff08;看不到物理地址&#xff0c;只能看看虚拟地址喽&#xff09;2.虚拟地址空间布局&#xff08;五个段&#xff09;3.感性理解一下虚拟地址空间&#xf…

【C++修炼之路】C++入门(上)

&#x1f451;作者主页&#xff1a;进击的安度因 &#x1f3e0;学习社区&#xff1a;进击的安度因&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录一、前言二、第一个 C 程序三、C 关键字(C98)四、命名空间1、命名空间的定义2、命名空间…

C++ Prime课后习题第一章编程

编程一个C程序&#xff0c;它显示您的姓名和地址。#include <iostream>int stonetolb(int); int main() {using namespace std;cout << "zzz ";cout << "闵行"<<endl;return 0; }编写一个程序&#xff0c;要求用户输入一个以long…