2023.10.21 关于 阻塞队列

news2024/11/21 0:19:38

目录

阻塞队列

优先级队列(Priority Queue)

阻塞队列(Blocking Queue)

消息队列(Message Queue)

生产者消费者模型

生产者消费者模型的两个好处

标准库阻塞队列使用

实现一个简单 生产者消费者模型

自己实现阻塞队列代码 


阻塞队列

  • 队列最基本的原则为先进先出

优先级队列(Priority Queue)

  • 该队列为一种特殊的队列数据结构
  • 每个元素都有一个与之相关联的优先级
  • 在优先级队列中,元素按照优先级的顺序进行插入和删除操作,优先级高的元素先被处理
  • 所以通常情况下优先级队列的实现使用 数据结构

阻塞队列(Blocking Queue)

  • 该队列也是特殊的队列
  • 虽然也遵循先进先出的原则,但带有特殊的功能:阻塞
  • 当一个线程试图对空队列进行取元素操作时,它会被阻塞,直到有另一个线程向队列中插入了一个元素
  • 当一个线程试图向满队列中插入元素时,它会被阻塞,直到有另一个线程取走了一个元素,释放了空间

消息队列(Message Queue)

  • 该队列也属于特殊的队列
  • 相当于是在阻塞队列的基础上,加上了个 消息的类型 按照制定类别进行先进先出

  • 因为消息队列很好用,所以就有大佬把这样的数据结构,单独实现成了一个程序
  • 这个程序可以通过网络的方式和其他程序进行通信
  • 有时这个消息队列,可以单独部署到一组服务器上(分布式)
  • 此时其 存储能力 和 转发能力 都大大提升了
  • 因此很多大型项目里,都可以看到 消息队列 的身影
  • 此时 消息队列,就已经成为了一个可以和 mysql,redis 这种相提并论的一个重要组件,也就是 "中间件"
  • rabbit mq 就是消息队列中的一种典型实现

生产者消费者模型

  • 消息队列之所以很好用,这与阻塞队列的阻塞特性有很大的关系
  • 基于这样的特性,可以实现 生产者消费者模型


生产者消费者模型的两个好处

  • 好处一:实现了发送方和接收方之间的 解耦

  • 此时A 把请求转发个 B处理,B 处理完把结果反馈给A
  • 此时可以视为 A调用了B
  • 在上述场景中,A和 B之间的耦合是比较高的
  • A要调用B ,A务必要知道 B的存在
  • 如果 B挂了,很容易引起 A的 bug

  • 如果要是再加一个 C服务器,此时也需要对 A修改不少代码
  • 因此就需要针对 A重新修改代码、重新测试、重新发布、重新部署 等,十分麻烦 

使用生产者消费者模型解耦

  • 此时A 和B 之间的耦合就降低了很多
  • A 并不知道B,A只知道队列(A的代码中没有任何一行与 B相关)
  • B 也不知道A,B只知道队列(B的代码中没有任何一行与 A相关)
  • 如果 B挂了,对 A没有任何影响,因为队列在,A 仍然可以给队列插入元素,如果队列满了,就先阻塞
  • 如果 A挂了,对 B没有任何影响,因为队列在,B仍然可以从队列中取元素,如果队列空了,就先阻塞
  • 新增一个服务器C 来作为消费者,对于 A来说是无感知的

  • 好处二:可以做到 削峰填谷,保证系统的稳定性

  • 我们进行服务器开发,也和上述这个模型是非常相似的
  • 上游就是用户发送的请求
  • 下游就是一些执行具体业务的服务器
  • 用户发多少请求是不可控的

标准库阻塞队列使用

import java.util.concurrent.*;

//阻塞队列的使用
public class ThreadDemo21 {
    public static void main(String[] args) throws InterruptedException {
//        基于链表实现的阻塞队列
        BlockingQueue<String> blockingQueue1 = new LinkedBlockingQueue<>();

//        基于数组实现的优先级队列
//        BlockingQueue<String> blockingQueue2 = new ArrayBlockingQueue<>();
//        基于堆实现的优先级队列
//        BlockingQueue<String> blockingQueue3 = new PriorityBlockingQueue<>();

        blockingQueue1.put("hello");
        String res = blockingQueue1.take();
        System.out.println(res);
        res = blockingQueue1.take();
        System.out.println(res);
    }
}
  • 我们向阻塞队列中使用 put 方法插入了一个 "hello" 字符串
  • 此时我们再连续使用两次 take 方法取出阻塞队列中元素
  • 注意此时的 put 方法 和 take 方法均带有阻塞功能

运行结果:

  • 我们可以发现控制台打印插入的 "hello" 字符串
  • 当执行第二次 take 方法时,便进行阻塞

实现一个简单 生产者消费者模型

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ThreadDemo22 {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

//        创建两个线程来作为 生产者 和 消费者
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    Integer result  = blockingQueue.take();
                    System.out.println("消费元素:" + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();


        Thread producer = new Thread(() -> {
            int count = 0;
            while (true) {
                try {
                    blockingQueue.put(count);
                    System.out.println("生产元素:" + count);
                    count++;
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
    }
}

运行结果:

自己实现阻塞队列代码 

  • 以下是基于一个循环数组实现的阻塞队列
//自己写阻塞队列
//此处不考虑泛型,直接使用 int 来表示元素类型
class MyBlockingQueue {
    private int[] items = new int[10];
//    指向阻塞队列的头部
    private int head = 0;
//    指向阻塞队列的尾部
    private int tail = 0;
//    记录阻塞队列的长度
    private int size = 0;

//    入队列
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            while (size == items.length) {
//            队列满了 此时需要阻塞
//                return;
                this.wait();
            }
            items[tail] = value;
            tail++;
//        针对 tail 的处理
//        1.
//        tail = tail % items.length
//        2.
            if(tail >= items.length) {
                tail = 0;
            }
            size++;
//            用来唤醒 take 中的 wait
            this.notify();
        }
    }

//    出队列
    public Integer take() throws InterruptedException {
        int result = 0;
        synchronized (this) {
            while (size == 0) {
    //            队列为空 应该进行阻塞
//                return null;
                this.wait();
            }
            result = items[head];
            head++;
            if (head >= items.length) {
                head = 0;
            }
            size--;
//            唤醒 put 中的 wait
            this.notify();
        }
        return result;
    }
}

public class ThreadDemo23 {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue();

        Thread product = new Thread(() ->{
            int count = 0;
            while (true) {
                try {
                    System.out.println("生产元素 : " + count);
                    queue.put(count);
                    count++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        product.start();

        Thread custom = new Thread(() -> {
            int result = 0;
            while (true) {
                try {
                    result = queue.take();
                    System.out.println("消费元素 : " + result);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        custom.start();
    }
}

运行结果:


重点理解

  • 正因为生产者消费者模型是边生产边消费的
  • 所以上述阻塞队列我们可以基于一个循环数组
  • 那么当我们的 tail 指向数组末尾时,便需要重新指向开头,此时便有两种方案
  • 方案一 根据 tail 的值和 阻塞队列的长度来取模
  • 方案二 直接将 tail 值 与 数组长度进行比较,如果等于或大于数组长度,直接赋值为 0,也就相当于直接从头开始
  • 更推荐第二种方案
  • 首先第二种方案要更加直观,可读性更好,一眼便能知道该段代码的逻辑是什么
  • 其次取模运算本质上要做一系列的除法运算,计算机算乘除相对算加减是要慢的,但是方案二仅是判定和赋值,这两个高效操作,从而方案二的效率比方案一要高

  • 为了实现 put 方法和 take 方法的阻塞效果,我们引入了 wait 和 notify 
  • 在仅创建一个阻塞队列实例的情况下,那么这个两个线程中的 wait 是否会同时触发?
  • 肯定是不会的,针对同一个阻塞队列,不可能既是空又是满

  • 此处当 队列满 或 队列空 时,将会直接进入 wait 阻塞状态
  • 那为什么这里是写的循环,而不是写 if 判断语句呢?
  • 具体来说就是当 wait 被 notify 唤醒后,阻塞队列一定是 非空 或 不满 吗?
  • 咱们当前的代码确实不会出现这种情况
  • 因为该代码一定是 插入或拿取 元素成功后才会 notify 唤醒 wait 
  • 并不存在第二种唤醒 wait 的方式,所以 wait 不会在非空 或 不满的时候被唤醒
  • 但是为了稳妥起见,还是加上 while 循环 唤醒之后,再判定一下,看此时的条件是否具备

  • 正如上述 wait 原码中建议写法一致

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

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

相关文章

【网络编程】基于epoll的ET模式下的Reactor

需要云服务器等云产品来学习Linux的同学可以移步/-->腾讯云<--/-->阿里云<--/-->华为云<--/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;新用户首次下单享超低折扣。 目录 一、Reactor介绍 二、基于epoll的ET模式下的Reactor计算器代码 1、Tcp…

C++之构造函数、析构函数、拷贝构造函数终极指南:玩转对象的诞生、生命周期与复制

W...Y的主页 代码片段分享 前言&#xff1a; 在上篇内容里&#xff0c;我们初识了C中的类与对象&#xff0c;了解了类的定义、类的实例化、 类的作用域等等&#xff0c;今天我们将继续深入了解类与对象的相关内容&#xff0c;学习构造函数、析构函数与拷贝构造函数&#xff…

WebGL笔记:图形转面的原理与实现

1 &#xff09;回顾 WebGL 三种面的适应场景 TRIANGLES 单独三角形TRIANGLE_STRIP 三角带TRIANGLE_FAN 三角扇备注 在实际的引擎开发中&#xff0c;TRIANGLES 是用得最多的TRIANGLES 的优势是可以绘制任意模型&#xff0c;缺点是比较费点 2 &#xff09;适合 TRIANGLES 单独…

Apache JMeter 安装教程

下载&#xff1a; 注意事项&#xff1a;使用JMeter前需要配置JDK环境 下载地址 下载安装以后&#xff0c;打开安装的bin目录 D:\software\apache-jmeter-5.4.1\apache-jmeter-5.4.1\bin&#xff0c;找到jmeter.bat&#xff0c;双击打开 打开后的样子 语言设置&#xff1a; 1…

windows模拟触摸

安装EcoTUIODriver驱动 GitHub - almighty-bungholio/EcoTUIODriver: Diver to convert tuio touch events into windows touch events. Started as GSoC 2012 project. 安装完后电脑属性显示笔和触控为为20点触摸点提供笔和触控支持。 在另一台电脑上运行tuio模块器是一个ja…

【iOS】JSON解析

JSON在Web开发和网络通信和传输中广泛应用&#xff0c;常用于存储和传输数据&#xff0c;这些数据一般也都是JSON格式&#xff0c;可以说绝大多数网络请求传输的数据都是JSON格式 在之前有关网络请求文章中&#xff0c;实现了网络数据加载流程&#xff0c;并对加载下来的JSON数…

Es集群部署

目录 组件全家套 版本说明 主机准备 1.解压安装 2.运行环境配置 2.1修改每个节点linux系统限制 2.2 修改每个节点 linux 系统配置 2.3 调整vm.max_map_count的大小 2.4 重启验证配置 3. 配置ES 3.1 每个节点创建ES用户&#xff0c;ES不能使用root启动 3.2 每个节点…

ELK概述部署和Filebeat 分布式日志管理平台部署

ELK概述部署、Filebeat 分布式日志管理平台部署 一、ELK 简介二、ELK部署2.1、部署准备2.2、优化elasticsearch用户拥有的内存权限2.3、启动elasticsearch是否成功开启2.4、浏览器查看节点信息2.5、安装 Elasticsearch-head 插件2.6、ELK Logstash 部署&#xff08;在 Apache 节…

数据库的基本知识理论

文章目录 一、数据库的演变史1.存取数据的演变史1.把数据存在了文件中2.存数据的文件越来越多&#xff0c;放在db文件夹3.数据库软件就能够解决以上所有问题 2.数据库软件应用史1.单机游戏2.网络游戏3. 集群 二、数据库1.什么是数据库2.数据库的作用1.实现数据共享2.减少数据的…

【Android知识笔记】Webview专题

WebView 核心组件 类名作用常用方法WebView创建对象加载URL生命周期管理状态管理loadUrl():加载网页 goBack():后退WebSettings配置&管理 WebView缓存:setCacheMode() 与JS交互:setJavaScriptEnabled()WebViewClient处理各种通知&请求事件should

系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第三部分:缓存

本心、输入输出、结果 文章目录 系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第三部分&#xff1a;缓存前言缓存数据存储在什么地方图层说明 为什么 Redis 这么快&#xff1f;如何使用 Redis顶级缓存策略弘扬爱国精神 系统设计 - 我们如何通俗的理解那些技术的运行原理…

嵌入式硬件中常见的100种硬件选型方式

1请列举您知道的电阻、电容、电感品牌&#xff08;最好包括国内、国外品牌&#xff09;。 电阻&#xff1a; 美国&#xff1a;AVX、VISHAY 威世 日本&#xff1a;KOA 兴亚、Kyocera 京瓷、muRata 村田、Panasonic 松下、ROHM 罗姆、susumu、TDK 台湾&#xff1a;LIZ 丽智、PHY…

SystemVerilog学习(4)——自定义结构

一、 通过typedef来创建用户自定义类型 typedef语句可以用来创建新的类型。例如,你要求一个算术逻辑单元(ALU)在编译时可配置,以适应8比特、16比特,24比特或32比特等不同位宽的操作数。在Verilog中,你可以为操作数的位宽和类型分别定义一个宏(macro),如例2.32所示。 SV则提供了…

Linux常用命令——clockdiff命令

在线Linux命令查询工具 clockdiff 检测两台linux主机的时间差 补充说明 在ip报文的首部和ICMP报文的首部都可以放入时间戳数据。clockdiff程序正是使用时间戳来测算目的主机和本地主机的系统时间差。 选项 -o&#xff1a;使用IP时间戳选项来测量系统时间差。时间戳只用3个…

超全全国所有城市人力资本测算数据集(1990-2021年)

参考《管理世界》中詹新宇&#xff08;2020&#xff09;的做法&#xff0c;本文对地级市的人力资本水平进行测算&#xff0c;其中人力资本水平用地级市的普通高等学校在校学生数占该地区总人口比重来反映 一、数据介绍 数据名称&#xff1a;地级市-人力资本测算 数据年份&…

基于指数分布优化的BP神经网络(分类应用) - 附代码

基于指数分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于指数分布优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.指数分布优化BP神经网络3.1 BP神经网络参数设置3.2 指数分布算法应用 4.测试结果…

计算一个Series序列元素的标准差Series.sem()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算Series序列中各元素的标准差 Series.sem() [太阳]选择题 关于以下代码的说法中正确的是? import pandas as pd a pd.Series([1,2,3]) print("【显示】a:\n",a) print(【执行】…

电路基础元件

文章目录 每周电子w5——电路元件基本电路元件电阻元件电容元件电感元件 每周电子w5——电路元件 基本电路元件 电路元件&#xff1a;是电路中最基本的组成单元 电路元件通过其端子与外部相连接&#xff1b;元件的特性则通过与端子有关的物理量描述每一种元件反映某种确定的电…

v-if和v-else-if、v-else或v-show

一、用于真正的隐藏和显示&#xff0c;显示时才会渲染dom元素 v-if"布尔类型条件"&#xff1a;能出现n次 v-else-if"布尔类型条件":必须与v-if搭配使用&#xff0c;出现n次 v-else&#xff1a;必须和v-if搭配使用&#xff0c;只能出现1次 例子1&#xff1…

LeetCode讲解篇之113. 路径总和 II

文章目录 题目描述题解思路题解代码 题目描述 题解思路 深度优先遍历二叉树&#xff0c;遍历的同时记录路径&#xff0c;直到遍历到叶节点&#xff0c;若路径和为targetSum则添加到结果集中 题解代码 func pathSum(root *TreeNode, targetSum int) [][]int {var res make([…