JavaEE-多线程编程阻塞队列

news2024/9/30 3:23:06

目录

生产者消费者模型

生产者消费者模型优势

通过代码看一下生产者消费者模型(使用阻塞队列)

自己实现阻塞队列


之前在数据结构中学的队列是最基础的队列,在实际开发中针对队列还有很多形式:(1)普通队列 ,线程不安全 (2)优先级队列:以堆为基础的数据结构,线程不安全 (3)阻塞队列:线程安全,先进先出,带有阻塞功能。当队列为空时尝试出队操作就会阻塞等待,一直阻塞到队列不为空为止。同理,当队列为满时尝试入队列也会阻塞等待,阻塞到队列不为满为止。BlockingQueue就是标准库提供的阻塞队列。(4)消息队列——生产者消费者模型

生产者消费者模型

由于消息队列这样的数据结构特别好用,实际开发中经常把这样的数据结构封装成单独的服务器程序中单独部署,这样的服务器程序同样也称为消息队列

消息队列能起到的作用——生产者消费者模型

生产者消费者模型在开发中主要有两方面的意义:

(1)能让程序解耦合  (2)能够使程序“削峰填谷”

普通的队列也可以实现这个模型,具体要看实现的场景,如果需要在一个进程内实现生产者消费者模型的话直接使用阻塞队列即可,如果需要在分布式系统实现生产者消费者模型那就需要单独部署的消息队列服务器。

生产者消费者模型是一种典型的解决多线程问题的方式。比如说:当ABC三人分工合作包饺子,包饺子需要先擀皮再包,但擀面杖只有一个,ABC三人难免会去竞争擀面杖,此时用专业术语来讲就是“锁竞争”,如果分配A来擀皮,擀完的皮放到面板上让B和C来包,这个时候A就是生产者,B和C就是消费者,面板就相当于是阻塞队列/消息队列;如果A擀的块,B和C包得慢,当面板放满的时候A就陷入阻塞等待,B和C在面板上消费后A才能继续生产,同理B和C包的比A擀得快的话,BC就会阻塞等待A生产,A->面板->BC此时就是生产者消费者模型。

生产者消费者模型优势

生产者消费者模型有很多优势,最主要的两个:

(1)解耦合

当有线程A和线程B,让A直接调用B意味着A代码中要包含很多B的逻辑,B代码里也要包含A相关的逻辑,彼此间会有一定的耦合。一旦对A进行修改就可能会对B造成影响,一旦A出bug也可能会牵连到B,这种情况下的模型可以简单画为:

但如果采用生产者消费者模型,在AB中间加一块“面板”,此时可以画为:

此时站在A的视角,只需要关心和队列的交互,站在B的视角也只需要关心和队列的交互。如果对A的代码进行修改就不太能影响到B,大大降低了AB线程之间的耦合,未来如果引入别的线程也不需要对A进行修改,直接让新线程从队列中读取数据就可以。提高了代码的可扩展能力

(2)削峰填谷

客户端发来的请求数量多少没法提前预知,遇到突发事件可能会导致客户端发来的请求激增。A来接受客户端发来的大量请求,然后将请求发到消息队列,B从消息队列获取任务去执行,无论A给队列写多快,B都能按照自己的节奏消费数据,相当于队列将B保护了起来。

正常情况下,A收到一个客户端请求也会请求一次B,A收到的请求激增了B的请求也会激增,A的工作比较简单消耗资源少,B的工作更复杂消耗的资源更多,一旦请求多了就容易挂。

挂:服务器每次处理一个请求都要消耗一定的系统资源,如果同一时刻要处理的请求太多,消耗的总资源数目超出机器能提供的上线,机器就挂了)

三峡大坝起到的效果就是削峰填谷。

通过代码看一下生产者消费者模型(使用阻塞队列)

使用标准库提供的BlockingQueue

ArrayBlockingQueue->使用数组

LinkedBlockingQueue->链表

PriorityBlockingQueue->堆

主要使用put&take,其他的offer&add&poll是继承原队列的方法,不带有阻塞功能,看BlockingQueue的源码可以看到继承了Queue接口。

自己实现阻塞队列

阻塞队列是线程安全带有阻塞功能的普通队列,所以自行实现类似于阻塞队列的方法时可以用以下几步:(1)写一个普通队列 (2)加上线程安全 (3)加上阻塞功能

第一步:使用数组实现普通队列,用两个变量记录队首队尾的下标,还需引入一个变量记录当前数组的元素个数,记录满/空。第一步的代码写完后为:

class MyBlockingQueue{
    private int[] array = null;
    private int head;
    private int tail;
    private int size;

    void put(int num){
        if(array.length >= size ){ 
            return; //当队列为满时入队操作应该阻塞等待,后续步骤再进行修改
        }
        array[tail] = num;
        tail++;
        if(tail >= array.length){
            tail = 0;
        }
        size++;
    }
    int take(){
        if(size == 0){
            return 0; //当队列为空时出队列应该阻塞等待,我们先完成第一步,后续再进行修改
        }
        int ret = array[head];
        head++;
        if(head >= array.length){
            head = 0;
        }
        size--;
        return ret;
    }

}

第二步:将线程安全和阻塞的逻辑加上,代码为:

class MyBlockingQueue{
    private int[] array = null;
    private volatile int head;
    private volatile int tail;
    private volatile int size;

    void put(int num) throws InterruptedException {
        synchronized (this){ //(2)给关键操作加锁,加上线程安全
            if(array.length >= size ){
                this.wait(); //(1)当队列满时,当前线程进入等待,当队列被take时解除等待
            }
        }
        array[tail] = num;
        tail++;
        if(tail >= array.length){
            tail = 0;
        }
        size++;
        this.notify();
    }
    int take() throws InterruptedException {
        synchronized (this){
            if(size == 0){
                this.wait();
            }
        }
        int ret = array[head];
        head++;
        if(head >= array.length){
            head = 0;
        }
        size--;
        this.notify();//(3)将阻塞中的线程唤醒
        return ret;
    }

}

逻辑步骤已经在代码中进行表示

需要注意(1)唤醒时使用notifyall是不合理的,比如说有两个线程put时被阻塞了,一次take将所有等待线程唤醒了,一个线程可以put成功,而另一个put就要出bug了

(2)当鼠标放在wait上时可以看见系统给出的信息

大概意思是wait除了会被notify&notifyall唤醒,还会被别的interrupt唤醒,搭配while使用可以在被唤醒之后再次进行判定是否符合唤醒条件,是否该继续进行。将if改为while就是我们自己实现的MyBlockingQueue的最后一步优化。

最后优化完的代码为:

class MyBlockingQueue{
    private int[] array = null;
    private volatile int head;
    private volatile int tail;
    private volatile int size;

    void put(int num) {
        synchronized (this){ 
            while(array.length >= size ){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        array[tail] = num;
        tail++;
        if(tail >= array.length){
            tail = 0;
        }
        size++;
        this.notify();
    }
    int take() {
        synchronized (this){
            while(size == 0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        int ret = array[head];
        head++;
        if(head >= array.length){
            head = 0;
        }
        size--;
        this.notify();
        return ret;
    }

}

本篇文章到这里就结束了,感谢观看。

下篇文章更新线程池相关内容

感谢观看

道阻且长,行则将至

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

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

相关文章

PyCharm | PyCharm中创建带有注释的py文件

文章目录 0 问题引入1 问题解决1.1 在Pycharm里进行设置1.2 模板1.3 可选参数 2 效果展示 0 问题引入 想要创建带注释的py文件,该如何解决呢? 1 问题解决 1.1 在Pycharm里进行设置 打开Pycharm的Seetings按照如图所示来操作 1.2 模板 # -*- codin…

【大模型从入门到精通3】openAI api的入门介绍3

这里写目录标题 理论问题实践问题任务 1: 基本的 API 请求任务 2: 安全处理 API 密钥任务 3: 解释 API 响应任务 4: 强大的错误处理任务 5: 交互式聊天界面任务 6: 响应后处理任务 7: 动态内容生成任务 8: 优化与监控 理论问题 整合OpenAI的API到应用中为机器学习工程师、数据…

python运行js之execjs基本使用

python运行js之execjs基本使用 现在大部分网站都使用JS加密和JS加载的情况,数据并不能直接被抓取出来,这时候就需要使用第三方类库来执行JS语句。 官网:https://pypi.org/project/PyExecJS/ 使用前提:电脑需要安装 Node.js 一、安…

20240805 每日AI必读资讯

世界首例!AI机器人做牙科手术,8倍速诊疗比人类医生更精准 - Perceptive:让人工智能控制的自主机器人,首次对人类患者进行了全过程的牙科手术,速度大约是人类牙医的8倍。 - 两项新技术 1、OCT 3D成像系统:…

【MySQL进阶】MySQL主从复制

目录 MySQL主从复制 概念 主从形式 一主多从 多主一从 双主复制 主从级联复制 主从复制原理 三个线程 两个日志文件 主从复制的主要工作模式 异步复制 半同步复制 全同步复制 MySQL主从复制 概念 MySQL主从复制是一种数据分布机制,允许从一个数据库服…

Chapter 26 Python魔术方法

欢迎大家订阅【Python从入门到精通】专栏,一起探索Python的无限可能! 文章目录 前言一、什么是魔术方法?二、常见的魔术方法① __init__构造方法② __str__字符串方法③ __lt__比较方法④ __le__比较方法⑤ __eq__比较方法 前言 本章将详细讲…

RabbitMQ高级特性 - 消费者消息确认机制

文章目录 RabbitMQ 消息确认机制背景消费者消息确认机制概述手动确认(RabbitMQ 原生 SDK)手动确认(Spring-AMQP 封装 RabbitMQ SDK)AcknowledgeMode.NONEAcknowledgeMode.AUTO(默认)AcknowledgeMode.MANUAL…

思源笔记软件的优缺点分析

在过去一年里,我用了很多款笔记,从word文档到onenote到语雀再到思源,最后坚定的选择了思源笔记 使用感受 首先是用word文档来记笔记,主要是开始时不知道笔记软件怎么好用,等到笔记越来越膨胀的时候我发现&#xff0c…

2024死磕小红书,一定能赚到钱!

​2024死磕小红书,一定能赚到钱!在文末领取小红书运营完全指南电子书 从2023年起,小红书这股热乎劲儿就像开了挂,突然间就成了人人想蹭的“显学”。大伙儿都想趁着平台红利期,分一杯羹。说来惭愧,我从2020年…

C语言指针·高级用法超详解(指针运算、野指针、悬空指针、void类型指针、二级以及多级指针)

目录 1. 指针的运算 2. 野指针和悬空指针 2.1 野指针 2.2 悬空指针 3. void类型指针 4. 二级指针和多级指针 4.1 命名规则 4.2 作用 4.2.1 二级指针可以操作一级指针记录的地址 4.2.2 利用二级指针获取变量中记录的数据 1. 指针的运算 文章开始前可以先了…

基于强化学习的Deep-Qlearning网络玩cartpole游戏

1、环境准备,gym的版本为0.26.2 2、编写网络代码 # 导入必要的库 import gym import torch import torch.nn as nn import torch.optim as optim import numpy as np from collections import deque import random# 定义DQN网络 class DQN(nn.Module):def __init__…

《深入浅出WPF》学习笔记五.Mvvm设计模式

《深入浅出WPF》学习笔记五.Mvvm设计模式 背景 在通过视频学习wpf的过程中,讲师花了不少篇幅来讲Mvvm。特地在此用自己的语言总结一番,方便以后面试回答,如有理解不对,欢迎指正哈。 Mvvm结构 Mvvm指的是ModelViewViewModel 为什么要使用…

《网络安全自学教程》- MySQL匿名用户的原理分析与实战研究

《网络安全自学教程》 低版本的MySQL数据库在安装时会创建一个用户名和密码为空的账户,也就是匿名账户。即使升级到高版本,匿名账户仍然会存在。 MySQL匿名账户 1、检查是否存在匿名账户2、检查用户权限3、创建匿名账户4、使用匿名账户登录5、删除匿名账…

医院管理系统

医院管理系统 本文所涉及所有资源均在传知代码平台可获取 文章目录 医院管理系统概述使用技术核心功能1. 登录与注册2. 管理员系统3. 患者系统(医院电子平台)4. 医生系统(坐诊系统) 部署与启动适用场景 概述 本项目是一个专为大学…

读零信任网络:在不可信网络中构建安全系统09用户信任

1. 用户信任 1.1. 将设备身份和用户身份混为一谈会导致一些显而易见的问题 1.1.1. 特别是当用户拥有多台设备时,而这种情况很普遍 1.1.2. 应该针对不同类型的设备提供相匹配的凭证 1.1.3. 在存在共用终端设备的情况下,所有的这些问题将更加凸显 1.2…

打造未来交互新篇章:基于AI大模型的实时交互式流媒体数字人项目

在当今数字化浪潮中,人工智能(AI)正以前所未有的速度重塑我们的交互体验。本文将深入探讨一项前沿技术——基于AI大模型的实时交互式流媒体数字人项目,该项目不仅集成了多种先进数字人模型,还融合了声音克隆、音视频同步对话、自然打断机制及全身视频拼接等前沿功能,为用…

Python中使用正则表达式

摘要: 正则表达式,又称为规则表达式,它不是某种编程语言所特有的,而是计算机科学的一个概念,通常被用来检索和替换某些规则的文本。 一.正则表达式的语法 ①行定位符 行定位符就是用来描述字符串的边界。"^&qu…

第十三节、人物属性及伤害计算

一、碰撞器层级剔除 选中player和敌人,即可去除 若勾选触发器,则会取消掉碰撞效果,物体掉落 二、人数属性受伤计算 1、创建代码 将两个代码挂载到玩家和敌人身上 2、调用碰撞物体的方法 3、伤害值 开始:最大血量即为当前血量…

Arduino PID库 (6):初始化

Arduino PID库 (6):初始化 参考:手把手教你看懂并理解Arduino PID控制库——初始化 Arduino PID库 (5):开启或关闭 PID 控制的影响 问题 在上一节中,我们实现了关闭和打开PID的功…

最小二乘法求解线性回归问题

本文章记录通过矩阵最小二乘法,求解二元方程组的线性回归。 假设,二维平面中有三个坐标(1,1)、(2,2)、(3,2),很显然该三个坐标点不是…