javaEE初阶 — 阻塞队列

news2024/11/26 22:23:53

文章目录

  • 阻塞队列
    • 1. 概念与特性
    • 2. 生产者与消费者模型
      • 2.1 生产者消费者模型的两个好处(主要的)
    • 3. 标准库中的阻塞队列
    • 3.1 代码实现生产者消费者模型
    • 4. 阻塞队列实现
      • 4.1 普通队列实现
      • 4.2 给队列追加阻塞功能

阻塞队列

1. 概念与特性


阻塞队列 是一种特殊的队列,也遵守 “先进先出” 的原则。

阻塞队列 是一种线程安全的数据结构,虽然也是 先进先出 的,
但是它还带有 阻塞 功能。

两个特性:

  • 队列满 的时候,继续 入队列就会阻塞,直到有其他线程从队列中取走元素。
  • 队列空 的时候,继续 出队列也会阻塞,直到有其他线程往队列中插入元素。

2. 生产者与消费者模型


生产者消费者模式就是通过一个 容器 来解决生产者和消费者的 强耦合问题

生产者和消费者彼此之间不直接通讯,而通过 阻塞队列 来进行通讯,
所以 生产者 生产完数据之后不用等待 消费者 处理,直接 扔给阻塞队列
消费者 不找 生产者 要数据,而是直接 从阻塞队列里取

举一个例子

过年包饺子的时候,妈妈负责擀饺子皮,并且将擀好的饺子皮放到盖帘上,
爸爸和孩子就直接到盖帘上拿饺子皮开始包饺子。

这里的 妈妈 就相当于是 生产者 ,爸爸和孩子 就相当于是 消费者
盖帘 就相当于是 阻塞队列

如果此时 妈妈 擀的速度太快了,盖帘的空间就会占满,妈妈就要等爸爸和孩子把饺子皮拿走后,
盖帘有空间了,再继续擀饺子皮。

如果此时 爸爸和孩子 包的速度太快了,盖帘上没有饺子皮了,
此时就要等待着妈妈把饺子皮擀好,才能继续包饺子。

2.1 生产者消费者模型的两个好处(主要的)


1、实现了发送方与接收方的之间的解耦合

这里的 耦合 指的是代码不同模块之间的关系
如果某一个模块的代码发生了改变,对其他模块影响的大小。

比方说你的女朋友生病了,这个时候那就要照顾她,在这个时候她对于我还有很大的关系的。
如果哪一天分手了,她再生病,对于我就没有这么大的关系了。

当然,写代码的时候尽量是 低耦合降低耦合 的过程就叫做 解耦合

比如解开缠在一起的耳机线。

开发中典型的场景:服务器之间的相互调用



此时就可以视为A调用了B。
A服务器接收到充值请求后转发给B服务器处理,B服务器处理完了再把结果反馈给A服务器。

上述场景中耦合性是比较高的。

A 调用 B 的前提是务必要知道 B 是存在的,如果 B 挂了,此时就很容易引起BUG。

针对上面的场景使用 生产者消费者模型 就可以有效的降低耦合。



此时 A 和 B 之间的耦合就降低了很多。
A 不知道 B 的存在,A 只知道队列(A 的代码中没有任何一行代码与 B 的相关)
B 不知道 A 的存在,B 只知道队列(B 的代码中没有任何一行代码与 A 的相关)

如果此时 B 挂了,对于 A 不会有任何影响,因为队列还好着,
A 仍然可以给队列插入元素,如果队列满了,就先阻塞就好。

如果此时 A 挂了,对于 B 也不会有任何影响,因为队列还好着,
B 仍然可以给队列插入元素,如果队列满了,就先阻塞就好。

A B 任何一方挂了都不会对对方造成影响。


2、削峰填谷,保证系统的稳定性



削峰填谷 就是把波峰削低一点,把波谷填高一点。

三峡大坝就有 削峰填谷 的作用。



如果上游水多了,就关闸蓄水,此时就相当于由三峡大坝承担了上游的冲击,
对下游起到了很好的保护作用,这就是 削峰

如果上游少水了,三峡大坝就卡闸放水,有效保证下游的用水情况,避免出现干旱灾害。
这就是 填谷

用服务器进行开发,也是和上述模型是非常相似的。

上游就是用户发送的请求,下游就是一些执行具体的业务的服务器。
用户发送多少个请求,是不可控的,有时多有时少。

如果哪天超过了服务器的上限,说不定就会挂了,这个时候就可以采用生产者消费者模型来解决。

3. 标准库中的阻塞队列


BlockingQueue 是一个接口,真正实现的类是 LinkedBlockingQueue

put 方法用于阻塞式的入队列,

take 用于阻塞式的出队列。

BlockingQueue 也有 offer、poll、peek 等方法,但是这些方法不带有阻塞特性。


代码实现

package thread;

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

public class ThreadDemo1 {

    public static void main(String[] args) throws InterruptedException{
        BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
        blockingQueue.put("hello");
        System.out.println(blockingQueue.take());
    }
}




输出的结果即是,出队列拿到的结果。

如果连续出两次队列,此时队列就为空了,这是就会发生 阻塞。

3.1 代码实现生产者消费者模型


1、先创建两个线程来作为生产者和消费者

2、消费者要做的就是从队列里拿元素。

3、生产者的任务是往队列中放元素

整体代码

package thread;

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

public class ThreadDemo2 {

    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(); //启动
    }
}




根据输出结果可以看出要先生产才能消费,这是由于消费线程阻塞等待了。
在还没有生产出来之前就会一直阻塞。

4. 阻塞队列实现

4.1 普通队列实现


要想实现一个 阻塞队列 先要实现一个普通的队列



head 表示头部,tail 表示尾部。
此时的 head 和 tail 就组成了一个区间:[head, tail)

如果要插入一个元素(入队列),tail 的位置就往后移一个。


如果要拿出一个元素(出队列),head 的位置就往后移一个。



如果 tail 指向了数组的最后一个位置,此时在插入一个一个元素时,tail 就移动到最左边。





这也就形成了一个 循环队列

如何区分队列是空还是满

可以采取两个方法:

  • 浪费一个空间
    当 tail 走到 head 的前面是,队列就满了。
  • 引入一个 size 来记录元素的个数
    size 是 0 就是空的,size 是数组的长度就是满的。

下面是基于数组实现的队列。

完整代码

package thread;

// 次数不考虑泛型,只使用 int 来表示元素类型
class MyBlockQueue {
    private int[] items = new int[1000]; //要先有数组
    private int head = 0; //头部
    private int tail = 0; //尾部
    private int size = 0; //元素个数

    //入队列
    public void put(int value) {
        //如果数组空间满了就不能插入了
        if (items.length == size) {
            return;
        }
        items[tail] = value;
        tail++;

        //针对于tail位于数组末尾的处理
        if (tail >= items.length) {
            tail = 0; //重新放到数组的开头
        }
        size++; //队列元素个数加一个
    }

    //出队列
    public Integer take() {
        //如果队列是空的不能出队列
        if (size == 0) {
            return null;
        }
        int result = items[head];
        head++; //head向后移一个位置

        //针对head位于数组末尾位置的处理
        if (head >= items.length) {
            head = 0; //重新指向数组的开头
        }
        size--; //元素个数减一个
        return result;
    }
}
public class ThreadDemo3 {

    public static void main(String[] args) {
        MyBlockQueue myBlockQueue = new MyBlockQueue();
        //入队列
        myBlockQueue.put(1);
        myBlockQueue.put(2);
        myBlockQueue.put(3);

        //出队列
        int result = 0;
        result = myBlockQueue.take();
        System.out.println("result = " + result);
        result = myBlockQueue.take();
        System.out.println("result = " + result);
        result = myBlockQueue.take();
        System.out.println("result = " + result);
    }
}




关于 队列 更加详细的讲解请参考下面文章:

https://blog.csdn.net/m0_63033419/article/details/127828890?spm=1001.2014.3001.5502

4.2 给队列追加阻塞功能


1、为了保证线程安全需要给入队列和出队列操作加上锁( synchronized)。

2、入队列的时候,如果队列满了就会产生阻塞(wait 和 notify)。

  • 这个时候就要等到把队列里的元素拿出以后才可以入队列。
  • take 操作结束后,可以使用 notify() 来唤醒 put 操作中的 wait。
  • 这里不使用 sleep 是因为 sleep 只能是用于设置时间,
    而我们不知道会阻塞等待到什么时候。

3、出队列的时候,如果队列为空就阻塞(wait 和 notify)。

  • 这个时候就要等到入队列以后才可以出队列。

4、put 和 take 操作特殊情况的处理(while)。

当 wait 被唤醒的时候,此时 if 的条件一定就不成立了吗?
具体来说,put 中的 wait 被唤醒要求队列不满,但是 wait 被唤醒了之后,队列一定是不满的吗?

虽然当前的代码不会出现这样的情况,但是稳妥起见,最好的办法是:

wait 返回之后再次判断一下,看此时的条件是不是具备了。


完整代码

//入队列
public void put(int value) {
    synchronized (this) {
        //如果数组空间满了就不能插入了
        if (items.length == size) {
            //队列满了就要阻塞等待
            this.wait();
        }
        items[tail] = value;
        tail++;

        //针对于tail位于数组末尾的处理
        if (tail >= items.length) {
            tail = 0; //重新放到数组的开头
        }
        size++; //队列元素个数加一个
        //唤醒 take 中的 wait
        this.notify();
    }
}

//出队列
public Integer take() {
    int result = 0;
    synchronized (this) {
        //如果队列是空的不能出队列
        if (size == 0) {
            //队列为空就阻塞
            this.wait();
        }
        result = items[head];
        head++; //head向后移一个位置

        //针对head位于数组末尾位置的处理
        if (head >= items.length) {
            head = 0; //重新指向数组的开头
        }
        size--; //元素个数减一个
        //唤醒 put 中的 wait
        this.notify();
    }
    return result;
}

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

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

相关文章

Centos 安装Datax及Datax-web

异构数据的采集&#xff0c;方案还是比较多样&#xff0c;除了基于大数据平台的一些集成&#xff1b;简单的数据源&#xff0c;可以直接使用阿里开源的datax来实现&#xff1b;datax-web则是datax界面化操作的开源框架&#xff0c;集成了datax异构数据采集和任务调度的功能。关…

【Kubernetes | Pod 系列】 Pod 的生命周期 Ⅱ —— 容器重启策略

目录题5.4 容器重启策略示例&#xff08;1&#xff09;Always 策略&#xff08;2&#xff09;OnFailure 策略&#xff08;3&#xff09;Never 策略5.4 容器重启策略 在 Pod 的 YAML 清单的 spec 中包含一个 restartPolicy 字段&#xff0c;其可能取值包括 Always&#xff08;…

Jdbc配置文件连接mysql8.0——批量增删改查操作

目录 一、批量插入数据 (一)在DogDao中新增一个功能saveDogList (二)DogDaoImpl实现类中定义字符串拼接实现功能saveDogList (三)DogTest测试 (四)批量插入运行结果 (五)优化后的批量插入 1.DogDao接口中还是使用原来的新增功能saveDog 2. DogDaoImpl实现类中实现saveD…

DOS、DDos攻击详解

目录 一、DDOS 是什么&#xff1f; 二、DDoS的危害 三、常见的DOS攻击 四.DDoS的防范 一、DDOS 是什么&#xff1f; DoS为Denial of Service的简称&#xff0c;意思是拒绝服务。DoS攻击是一种使被攻击者无法正常提供服务的攻击 来解释一下&#xff0c;DDOS 是什么。 举例…

esxi6.0安装

一、安装exsi需要注意的事项&#xff1a; 1.进入BIOS做raid 不同硬件厂商进入bios的方式各不相同&#xff0c;请自行查阅相关资料 RAID 0的特点&#xff1a; 最少需要两块磁盘 数据条带式分布 没有冗余&#xff0c;性能最佳(不存储镜像、校验信息) 不能应用于对数据安全性要求…

代码随想录回溯总结

文章目录0、 前言1、回溯的定义2、回溯解决那些问题3、回溯模板4、问题详解4、1组合问题&#xff1a;[4.1.1 组合](https://leetcode.cn/problems/combinations/)[4.1.2 组合总和 II](https://leetcode.cn/problems/combination-sum-ii/)4.1.3 组合的其他问题4.2排列问题4.2.1[…

3.Isaac入门

Isaac入门 本节提供有关如何开始开发和运行 Isaac 应用程序的指南。 文章目录Isaac入门教程和示例应用程序运行应用程序应用程序控制台选项在 Jetson 上部署和运行Python 应用程序支持使用分布式工作区使用 Docker安装依赖创建 Isaac SDK 开发镜像教程和示例应用程序 有一个可…

【运维有小邓】实时告警通知

当网络上发生特定事件时&#xff0c;EventLog Analyzer可以通过多种方式进行响应。EventLog Analyzer可以实时生成告警 - 发送电子邮件或短信通知给指定的接收者 - 或运行由管理员提供的自定义脚本。通过所提供的多个选项&#xff0c;用户可以确保不会错过任何安全事件。EventL…

Python解题 - CSDN周赛第21期 - 接雨水

本期比赛都是比较基础的排序、查找&#xff0c;没有多少难度。不过有很多人反映第二题测试数据有问题&#xff0c;基本所有选手在本题上都没得分。最近官方每期比赛都会有类似的数据问题&#xff0c;虽然参赛者对数据有疑问&#xff0c;但从未得到解答&#xff0c;官方也未曾公…

十年云深时:天翼云的成长密钥

工业时代&#xff0c;我们通常以“用电量”作为指标&#xff0c;来衡量一个地方的经济发展水平。数字经济时代&#xff0c;“用云量”则与数字经济规模呈正相关。因此&#xff0c;中国数字化浪潮的持续推进&#xff0c;也让云计算行业的重要性与市场竞争性不断提升&#xff0c;…

【C语言】交换奇偶位和 offsetof 宏的实现

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《阿亮爱刷题》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;交换奇偶…

2021年 APP个人信息使用态势分析报告

声明 本文是学习2021年APP个人信息使用态势分析报告. 下载地址 http://github5.com/view/55008而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 App违规行为处罚案例 某赚钱类App未明示信息收集规则被罚 经查公安机关调查&#xff0c;某网络科技公司开…

2023 CPA 会计-存货【刷题】

1. 存货的确认和初始计量 大元公司为增值税一般纳税人&#xff0c;增值税税率为13%。 这句话好像没有用 2019年购入材料300公斤&#xff0c;收到增值税发票注明价款1800万元&#xff0c;增值税税额是234万元。 增值税是在价款的基础上收取的&#xff0c;所以这批材料的价值就是…

【收集】缩写合集

AABB 全称&#xff1a;Axis Aligned Bounding Box参考 https://blog.csdn.net/qq_22822335/article/details/50930437AABB包围盒在游戏中&#xff0c;为了简化物体之间的碰撞检测运算&#xff0c;通常会对物体创建一个规则的几何外形将其包围。其中&#xff0c;AABB&#xff08…

【链表】关于链表,你该了解这些!

【链表】理论基础1 链表是什么1.1 单链表1.2 双链表1.3 循环链表2 链表的存储方式3 链表的定义3.1 C/C方式3.2 Java方式3.3 JavaScript方式4 链表的操作4.1 删除结点4.2 添加结点1 链表是什么 1.1 单链表 单链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两…

谷粒学苑项目-第一章数据库设计与项目结构

一、数据库设计 1、数据库 guli2、数据表 CREATE TABLE edu_teacher (id char(19) NOT NULL COMMENT 讲师ID,name varchar(20) NOT NULL COMMENT 讲师姓名,intro varchar(500) NOT NULL DEFAULT COMMENT 讲师简介,career varchar(500) DEFAULT NULL COMMENT 讲师资历,一句话说…

6个实用的红米手机技巧

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

盐湖提锂纳滤膜后钙镁离子深度处理技术

盐湖提锂&#xff1a;大规模、低成本&#xff0c;全球锂资源供应主体的理想来源锂作为自然界中轻、标准电极电势低、电化学当量大的金属元素&#xff0c;是天生理想的“电池金属”&#xff0c;因此在要求高比能的动力和储能应用场景中将具备长期的需求刚性&#xff0c;被誉为“…

Skywalking简单入门使用

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;Skywalking简单入门使用 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;50分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林…

软件测试/测试开发丨 | 想做App测试就一定要了解的App结构

性能测试 所谓的性能测试要在功能测试之后。 功能测试&#xff1a;关注能不能用 性能测试&#xff1a;关注好不好用 常见的性能关注点有&#xff1a; 接口响应时间&#xff1a;50毫秒 ~ 1000毫秒 吞吐量&#xff1a;1000万每天&#xff0c;2000万每天 ......10亿每天 TPS&a…