多线程(初阶七:阻塞队列和生产者消费者模型)

news2025/1/20 22:04:28

一、阻塞队列的简单介绍

二、生产者消费者模型

三、模拟实现阻塞队列

一、阻塞队列的简单介绍

首先,我们都知道,队列是先进先出的一种数据结构,而阻塞队列,是基于队列,做了一些扩展,在多线程有就非常有意义了

阻塞队列的特性:

(1)是线程安全的

(2)具有阻塞的特性

                ①当队列满了,这时不能往队列里放数据,就会阻塞等待,等队列的数据出队列后,这时队列没满,才能放数据。

                ②当队列空了,这时不能拿队列里的数据,就会阻塞等待,等有数据如队列了,这时队列不为空,才能拿数据。

这里,阻塞队列的用处非常大,基于阻塞队列的功能,就可以实现 “生产者消费者模型”。


二、生产者消费者模型

生产者消费者模型是一种很朴素的概念,描述的是一种多线程编程的方法。

1、举个栗子:

一家人包饺子,首先得和面,和完面就要开始擀饺子皮了,然后才开始包饺子,这里,因为一般家里也只有一个擀面杖,所以只能一个人擀饺子皮,其余的家人就会帮忙包饺子,假设一个人擀饺子皮,2个人包饺子,那么擀饺子皮的人就是生产者,包饺子的人就是消费者;这也就是消费者生产者模型。

一个桌子能发饺子皮的数量是有限的,当擀饺子皮的人比较快,桌子放满饺子皮后,就要等包饺子的人,消耗一些饺子皮来包饺子,才能继续擀饺子皮;而这也类似阻塞队列中队里满的情况。当包饺子的人包的比较快,桌子上的饺子皮都没了,就要等擀饺子皮的人擀了饺子皮后才能继续包饺子;而这也类似阻塞队列空的情况。不同的人分工不同,也类似多线程,各自干各自的事情。

2、引入生产者消费者模型的意义:

(1)解耦合

引入生产者消费者模型,就能更好的做到解耦合(把代码的耦合程度,从高降低,就称为解耦合)

在实际开发中,会涉及到 “分布式系统” ,服务器的整个功能不是又一个服务器完成实现的,而是由多个服务器,各自实现各自的一部分功能,再通过网络通信,把这些服务器联系起来,最终完成整个服务器的功能。粗糙流程如图:

而这时,入口服务器与A、B服务器的联系是密切相关的,请求要经过入口服务器,才能传达给A、B服务器,再A、B服务器拿到想要的数据,再返回给入口服务器,通过入口服务器,再把响应传给客户端。如果是这样,那如果请求突然骤升,这时超过入口服务器接收请求的峰值,这时入口服务器就挂了,入口服务器挂了后,A、B服务器拿不到请求,也会挂掉,这就体现了入口服务器和A、B服务器的耦合性比较高。

当我们在入口服务器和A、B服务器之间引入阻塞队列时,如图:

这时,如果入口服务器挂了,但是阻塞队列中还有请求的数据,至少不会因入口服务器挂了,A、B服务器也挂了,这样,入口服务器和A、B服务器的耦合性也就降低了。

(2)削峰填谷

如图,当客户端这边的请求突然骤增时,入口服务器都是比较能抗压的,但是也是有极限的,这时我们引入阻塞队列,就可以把这些请求数据都放进阻塞队列中,形成一个缓冲区,这样,即使外面的请求达到了峰值,也是由阻塞队列来承担,这样就形成了削峰填谷的效果。

注意:这时的阻塞队列,是基于阻塞队列这一数据结构,而实现的服务器程序,所以也叫消息队列

三、模拟实现阻塞队列

1、阻塞队列的简单介绍

在java标准库中,提供了现成的阻塞队列这一数据结构,如图:

是基于队列扩展而来的,队列有的,它也有;我们知道,入队列时可以用offer方法,出队列时可以用poll方法,在阻塞队列中,也有这两个方法,但是这两个方法是不带阻塞功能的;其中,在阻塞队列中,put是在阻塞功能的入队列,take方法是带阻塞功能的出队列

代码案例:

public class TestDemo1 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10);
        blockingQueue.put("aaa");
        String s1 = blockingQueue.take();
        System.out.println("第一个打印:s1 = " + s1);
        s1 = blockingQueue.take();
        System.out.println("第二个打印:s1 = " + s1);
    }
}

执行结果:

线程卡住不动了,原因是想要第二次出队列时,队列是空的,所以要等队列有元素入队列时,才能出队列,也就是说,这是带有阻塞功能的

2、实现阻塞队列

阻塞队列是通过循环队列实现的,而队列是依靠数组来实现的,这里的阻塞队列,我们只模拟实现其中的put和take方法。

(1)实现普通队列

代码:

// 为了简单, 不写作泛型的形式. 考虑存储的元素就是单纯的 String
class MyBlockingQueue {
    private String elems[] = null;
    private int head = 0;//记录头结点
    private  int tail = 0;//记录尾结点
    private int size = 0;//队列元素个数
    //构造方法,定义队列的容量大小
    public MyBlockingQueue(int capacity) {
        this.elems = new String[capacity];
    }
    //入队列
    public void put(String elem) {
        //判断容量满了没,满了就不能入队列,要阻塞等待
        if(size >= this.elems.length) {
            //阻塞等待,先不写,先实现普通功能的队列
            return;
        }
        //入队列
        elems[tail] = elem;
        tail++;
        //因为是循环队列,所以要判断尾巴有没有超过容量大小下标,超过了就要从0开始了
        if(tail > elems.length) {
            tail = 0;
        }
        //队列元素要++
        size++;
    }
    
    //出队列
    public String take() {
        String elem = null;
        //要判断队列是不是空的,空就不能出队列了,要阻塞等待
        if(size == 0) {
            //阻塞等待,因为是先实现普通队列的功能,所以后面再补充
            return null;
        }
        elem = elems[head];
        head++;
        //因为是循环队列,所以要判断头结点有没有超过容量大小下标,超过了就要0开始了
        if(head >= elems.length) {
            head = 0;
        }
        //出队列后,队列元素要--
        size--;
        return elem;
    }
}

每个步骤说明代码中有注释。

测试一下可不可以用,如图:

是可以用的,这样,普通的队列就已经搞好了

(2)加上线程安全

我们想想put和take里面要给哪里上锁,首先,写操作肯定是要加锁的,因为多线程同时执行写操作,肯定是线程不安全的,也就是下面这段代码:

接下来,我们讨论一下这两个代码要不要加锁,以take为例,如图:

我们画一下图,会比较好理解:

如果size = -1,是不符合我们预期的,size最小也只可能是0,不可能是-1,所以我要上面的判断条件也要加锁。

而put也一样,判断条件也要加锁。

代码:

class MyBlockingQueue {
    Object locker = new Object();
    private String elems[] = null;
    private int head = 0;//记录头结点
    private  int tail = 0;//记录尾结点
    private int size = 0;//队列元素个数
    //构造方法,定义队列的容量大小
    public MyBlockingQueue(int capacity) {
        this.elems = new String[capacity];
    }
    //入队列
    public void put(String elem) {
        synchronized (locker) {
            //判断容量满了没,满了就不能入队列,要阻塞等待
            if(size >= this.elems.length) {
                //阻塞等待,先不写,先实现普通功能的队列
                return;
            }
            //因为这些都是写操作,也有读操作,多线程并发执行时,写操作是线程不安全的,要把这些打包成一个原子,加锁
            synchronized (locker) {
                //入队列
                elems[tail] = elem;
                tail++;
                //因为是循环队列,所以要判断尾巴有没有超过容量大小下标,超过了就要从0开始了
                if(tail > elems.length) {
                    tail = 0;
                }
                //队列元素要++
                size++;
            }
        }
    }

    //出队列
    public String take() {
        String elem = null;
        //因为这些都是写操作,也有读操作,多线程并发执行时,写操作是线程不安全的,要把这些打包成一个原子,加锁
        synchronized (locker) {
            //要判断队列是不是空的,空就不能出队列了,要阻塞等待
            if(size == 0) {
                //阻塞等待,因为是先实现普通队列的功能,所以后面再补充
                return null;
            }
            elem = elems[head];
            head++;
            //因为是循环队列,所以要判断头结点有没有超过容量大小下标,超过了就要0开始了
            if(head >= elems.length) {
                head = 0;
            }
            //出队列后,队列元素要--
            size--;
            return elem;
        }
    }
}

(3)加上阻塞功能

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

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

相关文章

VMware下载安装教程

目录 一.下载二.安装 一.下载 官网地址&#xff1a;官网 下载的时候选择Workstation Player&#xff0c;这个是免费的&#xff0c;当然你也可以选择下载Workstation Pro。 二.安装 下载完成之后点击安装包按照需要安装即可。 安装之后启动&#xff0c;可以看到这个能够免费使…

微信小红书图片工具

因为平时喜欢发一些小红书和微信公众号的文章&#xff0c;但配图不是很方便。 当然了&#xff0c;有photoshop之类的重量级图片工具&#xff0c;但他们一般提供的选项很多&#xff0c;故而操作也比较复杂。 平时发文并不需要复杂的配图&#xff0c;质量要求也没有那么高&…

Task中Wait()和Result造成死锁

在使用Task的时候&#xff0c;一不留神就会造成死锁&#xff0c;而且难以发现&#xff0c;尤其是业务繁多的情况下&#xff0c;一个Task嵌套另一个Task的时候&#xff0c;下面就演示一下&#xff0c;在什么情况下&#xff0c;会产生Wait()和Result的死锁&#xff0c;因此&#…

代码随想录算法训练营 ---第五十二天

第一题&#xff1a; 简介&#xff1a; 动态规划五部曲&#xff1a; 1.确定 dp数组下标的定义 dp[i] 到达 i 时 最长递增子序列的长度 2.确定递推公式 我们确定当前的最大长度需要遍历前面所有的最大长度&#xff0c;然后如果序列最后一个值小于nums[i]那就dp[j] 1&#xf…

Kubernetes1.27容器化部署Prometheus

Kubernetes1.27容器化部署Prometheus GitHub链接根据自己的k8s版本选择对应的版本修改镜像地址部署命令对Etcd集群进行监控&#xff08;云原生监控&#xff09;创建Etcd Service创建Etcd证书的Secret创建Etcd ServiceMonitorgrafana导入模板成功截图 对MySQL进行监控&#xff0…

java+springboot物资连锁仓库经营商业管理系统+jsp

主要任务&#xff1a;通过网络搜集与本课题相关的素材资料&#xff0c;认真分析连锁经营商业管理系统的可行性和要实现的功能&#xff0c;做好需求分析&#xff0c;确定该系统的主要功能模块&#xff0c;依据数据库设计的原则对数据库进行设计。最后通过编码实现本系统功能并测…

进行主从复制时出现的异常FATAL CONFIG FILE ERROR (Redis 6.2.6)Reading the configuration file

错误如下所示&#xff1a; FATAL CONFIG FILE ERROR (Redis 6.2.6) Reading the configuration file, at line 1 >>> include/myredis/redis.conf Bad directive or wrong number of arguments出现错误的原因是.conf文件中命令之间缺少空格&#xff0c;如下所示&…

树莓派多串口通信

树莓派多串口通信 串口配置串口通信函数分析串口通信示例代码 参考博文1&#xff1a;树莓派 4 UART 多串口配置通信参考博文2&#xff1a;树莓派wiringPi库详解关于树莓派相关其他环境配置可参考&#xff1a;快速上手树莓派关于wiringPi库初始化与IO口开发可参考&#xff1a;树…

若依框架分页

文章目录 一、分页功能解析1.前端代码分析2.后端代码分析3. LIMIT含义 二、自定义MyPage,多态获取total1.定义MyPage类和对应的调用方法 一、分页功能解析 1.前端代码分析 页面代码 封装的api请求 接口请求 2.后端代码分析 controller代码 - startPage() getDataTable(…

智能优化算法应用:基于动物迁徙算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于动物迁徙算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于动物迁徙算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.动物迁徙算法4.实验参数设定5.算法结果6.参考…

HTML5+CSS3+Vue小实例:浪漫的心形文字动画特效

实例:浪漫的心形文字动画特效 技术栈:HTML+CSS+Vue 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" conte…

Linux 命令stat

命令作用 stat命令用于显示文件的状态信息。stat命令的输出信息比ls命令的输出信息要更详细。 查看的信息内容: File 显示文件名 Size 显示文件大小 Blocks 文件使用的数据块总数 IO Block IO块大小 regular file 文件类型&#xff08;常规文件&#xff09; Device …

SmartsoftHelp8,条形码,二维码 生成,解析 专业工具

生成条形码 生成二维码 条形码解析 二维码解析 专业工具 下载地址&#xff1a; https://pan.baidu.com/s/1zBgeYsqWnSlNgiKPR2lUYg?pwd8888

Web前端JS如何获取 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据

写在前面&#xff1a; 根据Web项目开发需求&#xff0c;需要在H5页面中&#xff0c;通过点击视频列表页中的任意视频进入视频详情页&#xff0c;然后根据视频的链接地址&#xff0c;主要是 .mp4 文件格式&#xff0c;在进行播放时实时的显示该视频的音频轨道情况&#xff0c;并…

Cairo编程语言

文章目录 Cairo编程语言一、背景二、什么是Cairo工作原理 三、Solidity 和 Cairo 的区别四、开发 Starknet 智能合约的工具链protostarstarknet-foundryScarb安装Scarb 安装starknet-foundry通过snfoundryupStarknet Foundry的snforge 命令行工具如何创建新项目、编译和测试snc…

笔记本电脑关闭触摸板

大部分人用笔记本 其实都是外接的鼠标 那么在打游戏 以及一些切图操作中 为了防止碰到触摸板导致误操作 我们就可以将他关掉 我们可以按快捷键 Win i 或者 点击桌面 此电脑/我的电脑/此计算机 打开设置界面 如果 左侧菜单中有设备 那么 直接点击设备 然后左侧菜单就会有 触摸…

openwrt上开启syslog打印方法

最近在openwrt上调试蓝牙时&#xff0c;出现问题&#xff0c;设备上的蓝牙适配器已经正常工作了&#xff0c;执行pair命令后&#xff0c;openwrt和待连接的设备上都出现了配对码&#xff0c;两边都同意&#xff0c;但连接失败 尝试分析log&#xff0c;发现在如下代码处打印了错…

【Vulnhub 靶场】【Funbox: Lunchbreaker】【简单】【20210522】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/funbox-lunchbreaker,700/ 靶场下载&#xff1a;https://download.vulnhub.com/funbox/FunboxLunchbreaker.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年05月22日 文件大小&#xff1a;1.6 GB 靶…

Docker镜像制作与推送

目录 Docker镜像制作 搭建私服 将本地镜像推送到私有库 Docker镜像制作 以创建一个新ubuntu镜像&#xff0c;并安装vim命令示例 运行一个ubuntu镜像&#xff0c;发现在镜像里面无法使用vim命令&#xff0c;因为该ubuntu镜像只包括了其最基本的内核命令 [rootlocalhost ~]…

BEVFormer环境配置

官网的教程说是Step By Step&#xff0c;但是实际上我按照步骤安装下来运行不了&#xff08;BEVFormer GitHub地址&#xff09;。主要是安装后关于包依赖产生的某些错误&#xff0c;特别是安装nuscenes-devkit没有在步骤中列出来&#xff0c;后面就不好解决某些包的版本依赖了。…