Java多线程案例之阻塞队列

【Java】阻塞队列 介绍阻塞队列的定义、种类、实现原理以及应用。 阅读详情

本篇文章将介绍Java多线程案例,阻塞队列,阻塞队列在普通队列的基础上多了两种情况,一是阻塞队列为空时,如果进行出队操作,会使当前线程阻塞,直到有新元素插入阻塞队列,该线程才被通知继续执行出队操作;二是阻塞队列为满时,如果进行入队操作,会使当前线程阻塞,直到有元素出队时,该线程才会被通知继续执行入队操作。在实际开发中,常常使用消息队列,而消息队列就是阻塞队列,只是在阻塞队列的基础上增加了很多功能。

:cherries:1.阻塞队列概论

:grapes:1.1阻塞队列的概念与作用

阻塞队列本质上还是一种队列,遵循先进先出,后进后出的原则,在此基础上,如果出队时阻塞队列为空,则会使当前线程陷入阻塞,直到入队新元素时通知线程继续执行,如果入队时阻塞队列为满,则会使当前线程陷入阻塞,直到出队旧元素时才通知线程进行执行。

:grapes:1.2标准库中阻塞队列类

java官方也提供了阻塞队列的标准类,主要有下面几个:

  • ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。
  • DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue: 一个不存储元素的阻塞队列。
  • LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
  • BlockingQueue接口: 单向阻塞队列实现了该接口。
  • BlockingDeque接口: 双向阻塞队列实现了该接口。

阻塞队列类的核心方法:

方法解释
void put(E e) throws InterruptedException带有阻塞特性的入队操作方法
E take() throws InterruptedException带有阻塞特性的出队操作方法
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException带有阻塞特性的入队操作方法,并且可以设置最长等待时间
E poll(long timeout, TimeUnit unit) throws InterruptedException带有阻塞特性的出队操作方法,并且可以设置最长等待时间
public boolean contains(Object o)判断阻塞队列中是否包含某个元素

其他一些普通队列的方法也支持,但是你都使用阻塞队列了,为什么还要使用普通队列的方法呢。

:grapes:1.3生产者消费者模型

这个模型怎么说呢,嗯...不好说直接看图吧。

生产者消费者是一种高内聚,低耦合的模型,这也是它的优势,特别是在服务器场景中,假设有两个服务器A(请求服务器),B(应用服务器),如果A,B直接传递消息,而不通过阻塞队列,那么当A请求突然暴涨的时候,B服务器的请求也会跟着暴涨,由于B服务器是应用服务器,处理的任务是重量级的,所以该情况B服务器大概率会挂。

但是,如果使用生产者消费者模型,那么即使A请求暴涨,也不会影响到B,顶多A挂了,应用服务器不会受到影响,这是因为A请求暴涨后,用户的请求都被打包到阻塞队列中(如果阻塞队列有界,则会引起队列阻塞,不会影响到B),B还是以相同的速度处理这些请求,所以生产者消费者模型可以起到“削峰填谷”的作用。了解清楚阻塞队列和生产者消费者模型,来简单实现一下,阻塞队列我们就基于数组实现吧,那么就先的实现循环队列。

:cherries:2.通过循环队列简单实现阻塞队列

:grapes:2.1循环队列的简单实现

循环队列是基于数组实现的,最重要的就是如何将队列为空状态与满状态区分开来,前面介绍数据结构的时候已经简单实现过了,现在就再简单复习一下,对队列不懂的,先好好学习队列。

区分判断空与满状态的方法如下:

不妨设对头索引为 front ,队尾索引为 rear ,顺序表长度为 len 。

方式1:记录队列元素个数 size ,当 size 的值与顺序表的大小相等时,代表队列已满。 size 值为 0 表示队列为空。

方式2:使用一个 boolean 类型的成员变量 flag 标记,初始为 false ,当每次入队时将 flag 设置为 true ,出队将 flag 设置为 false ,当 rear == front && flag == true 表示队列已满,当 rear == front && flag == false 表示队列为空。

方式3:牺牲一个单位的空间,在每次入队前判断 (rear+1)% len 是否与 front 相等,如果相等表示队列已满,如果 rear == front 则表示队列为空。

比如我按照方式1创建循环队列,大小为8,如图,size=0为空队列,size=8为满队列。

方式1最简单,我们通过方式1实现循环队列,阻塞队列最核心的就是出队和入队操作,我们重点实现这两个方法。

//循环队列
class MyCircularQueue {
    //队列数据
    private int[] elem = new int[100];
    //队头指针
    private int head;
    //队尾指针
    private int tail;
    //队列元素个数
    private int size;


    //出队头元素
    public Integer take() {
        if (size == 0) {
            //队列为空
            return null;
        }
        int ret = elem[head];
        head++;
        //作用等价于 head %= elem.length
        if (head >= elem.length) {
            head = 0;
        }
        size--;
        return ret;
    }

    //入队尾元素
    public void put(int val) {
        if (size == elem.length) {
            //队列满
            return;
        }
        elem[tail++] = val;
        //作用等价于 tail %= elem.length
        if (tail >= elem.length) {
            tail = 0;
        }
        size++;
    }
}
复制代码

:grapes:2.2阻塞队列的简单实现

目前上面实现的循环队列不是线程安全的,由于 take 与 put 方法都有写操作,直接无脑加锁。

//线程安全的循环队列
class MySafeCircularQueue {
    //队列数据
    private int[] elem = new int[100];
    //队头指针
    private int head;
    //队尾指针
    private int tail;
    //队列元素个数
    private int size;
    //专门的锁对象
    private final Object locker = new Object();

    //出队头元素
    public Integer take() {
        synchronized (locker) {
            if (size == 0) {
                //队列为空
                return null;
            }
            int ret = elem[head];
            head++;
            //作用等价于 head %= elem.length
            if (head >= elem.length) {
                head = 0;
            }
            size--;
            return ret;
        }
    }

    //入队尾元素
    public void put(int val) {
        synchronized (locker) {
            if (size == elem.length) {
                //队列满
                return;
            }
            elem[tail++] = val;
            //作用等价于 tail %= elem.length
            if (tail >= elem.length) {
                tail = 0;
            }
            size++;
        }
    }
}
复制代码

好了,重点来了,如何实现阻塞效果,关键是使用 wait 和 notify 机制:

入队时,队列为满需要使用 wait 方法使线程阻塞,直到有旧元素出队才使用 notify 通知线程执行。 出队时,队列为空需要使用 wait 方法使线程阻塞,直到有新元素入队才使用 notify 通知线程执行。

阻塞有界队列代码:

//基于循环队列实现阻塞队列
class MyBlockingQueue {
    //初始化循环队列
    private int[] elem = new int[100];

    //队头指针
    private int head;
    //队尾指针
    private int tail;
    //元素个数
    private int size;
    //专门的锁对象
    private final Object locker = new Object();


    //队头出元素,如果队列为空则阻塞
    public Integer take() throws InterruptedException {
        //循环队列为空,需要阻塞线程,直到循环队列入元素后才通知线程继续执行该操作
        synchronized (locker) {
            if (size == 0) {
                locker.wait();
            }
            int ret = elem[head++];
            if (head >= elem.length) {
                head = 0;
            }
            size--;
            //循环队列出元素后,队列就不为满了,可以通知线程继续进行入队操作
            locker.notify();
            return ret;
        }
    }
    //队尾入元素,如果队列满了,就阻塞
    public void put(int val) throws InterruptedException {
        //循环队列如果满了,则需要使线程阻塞,直到循环队列出元素后才通知线程继续执行该操作
        synchronized (locker) {
            if (size == elem.length) {
                locker.wait();
            }
            elem[tail++] = val;
            if (tail >= elem.length) {
                tail = 0;
            }
            size++;
            //循环队列入元素后,队列就不为空了,可以通知线程继续进行出队操作
            locker.notify();
        }
    }
}
复制代码

我们来简单实现一个生产者消费者模型来验证一下我们所实现的阻塞队列是否有问题。 生产者生产数字,消费者消费数字,为了使效果更加明显,我们把我们实现的阻塞队列的大小改为 3 ,即: private int[] elem = new int[3]; 。

我们使用 sleep 方法来模拟生产者消费者的生产或消费的频率。

情况1:生产者生产与消费者消费的频率一致

public class PCMod {
    private static final MyBlockingQueue queue = new MyBlockingQueue();
    public static void main(String[] args) {
        //生产者 每秒生产1个
        Thread producer = new Thread(() -> {
            int num = 0;
            while (true) {
                try {
                    System.out.println("生产了:" + num);
                    queue.put(num++);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
        //消费者 每秒消费1个
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int product = queue.take();
                    System.out.println("消费了:" + product);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();
    }
}
复制代码

 

因为生产者与消费者频率一致,所以生产者刚生产好,就立即消费者被消费了。

情况2:生产者生产频率比消费者消费的频率更快

public class PCMod {
    private static final MyBlockingQueue queue = new MyBlockingQueue();
    public static void main(String[] args) {
        //生产者  每秒生产1个
        Thread producer = new Thread(() -> {
            int num = 0;
            while (true) {
                try {
                    System.out.println("生产了:" + num);
                    queue.put(num++);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
        //消费者 每2秒消费1个
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int product = queue.take();
                    System.out.println("消费了:" + product);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();
    }
}
复制代码

运行结果:

 

因为生产者生产快,消费者消费慢,所以阻塞队列满了之后生产者需要等待消费者消费后才能生产,此时生产者步调与消费者一致。

情况3:生产者生产频率比消费者消费的频率更慢

public class PCMod {
    private static final MyBlockingQueue queue = new MyBlockingQueue();
    public static void main(String[] args) {
        //生产者  每秒生产1个
        Thread producer = new Thread(() -> {
            int num = 0;
            while (true) {
                try {
                    System.out.println("生产了:" + num);
                    queue.put(num++);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
        //消费者 每秒消费1个
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int product = queue.take();
                    System.out.println("消费了:" + product);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();
    }
}
复制代码

运行结果:

 

因为生产者生产慢,消费者消费快,所以阻塞队列为空后,消费者需要等待生产者生产,消费者才能消费,此时消费者步调与生产者一致。

Java 多线程5——阻塞式队列BlockingQueue的原理 + 消费者生产者模型 + 代码模拟实现 这篇文章教会你如何使用这个多线程中超级无敌好用的数据结构,带你认识消费者生产者模型,并带你手动用代码实现一个阻塞式队列!! 阅读详情

相关推荐

MAPPO实战:从理论到代码的协作智能体训练指南

本文详细介绍了MAPPO算法在多智能体协作训练中的实战应用,从理论基础到代码实现全面解析。通过对比PPO与MAPPO的差异,深入讲解CTDE框架、网络架构设计、参数调优等关键环节,并提供实际项目中的调参经验和常见问题解决方案,帮助开发者快速掌握这一强化学习前沿技术。

weixin_42561040的博客 267

深入浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque[转]

这个小节介绍Queue的最后一个工具,也是最强大的一个工具。从名称上就可以看到此工具的特点:双向并发阻塞队列。所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条件时挂起线程,这里说的队列是指支持FIFO/FILO实现的链表。 首先看下LinkedBlockingDeque的数据结构。通常情况下从数据结构上就能看出这种实现的优缺点,这样就知道如何更好的...

weixin_30877181的博客 185

【原创】Ubuntu20.04 安装 Isaac Gym 仿真器

Ubuntu20.04 安装 Isaac Gym

qq_54900679的博客 3096

双向阻塞队列BlockingDeque

前言前面我总结了一下关于BlockingQueue和BlockingQueue实现类的相关知识点,现在接下来看concurrent包的另一个接口BlockingDeque,BlockingDeque 接口表示一个线程安放入和提取实例的双端队列。就是说两段都可以进出的,实际上和单端的也差不多,就是单端变双端。 抛异常 特定值 阻塞 超时 插入 addFirst(o) offe

DO_IT_TODAY的博客 904

OPC UA 协议详解

OPC UA(统一架构)是新一代工业自动化标准通信协议,替代传统OPC Classic,具有跨平台、高安全性、标准化等优势。基于TCP协议,支持全平台部署,采用面向对象架构组织数据,提供实时读写、事件订阅、历史数据访问等功能。作为工业4.0核心协议,适用于智能制造、数字孪生等场景,能实现多品牌设备统一接入。相比Modbus和MQTT,OPC UA协议更规范安全但复杂度较高,是大型工业集群互联的主流选择。

eastyuxiao的专栏 732

USB PD快充功率监测仪:高精度嵌入式USB电压电流测量终端

USB供电已从数据接口演进为关键电源总线,尤其在PD快充场景下,工程师亟需对5V–20V宽压范围内的实时电压、电流及瞬时功率进行精准捕获。其底层原理依赖高共模抑制比的高边电流检测与分压采样+ADC数字化处理,结合LDO低噪声供电保障模拟链路精度。该类设备的技术价值在于以低成本实现协议无关的‘电气层可视化’,规避商用ASIC测试仪的黑盒局限,支撑PD充电验证、电源设计调试与教学实验等工程实践。本文介绍的开源USB表即基于N32G430 MCU与INA199电流检测芯片,覆盖USB-C输入/输出双通道监测,具备

weixin_42174098的博客 210

聊聊并发-Java中的阻塞队列

转自:http://www.infoq.com/cn/articles/java-blocking-queue1. 什么是阻塞队列?阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,...

weixin_33895695的博客 720

Livox MID-360与FAST-LIO2实战:从驱动部署到参数调优的完整指南

激光雷达惯性里程计(LIO)是机器人定位与建图(SLAM)领域的核心技术,它通过紧耦合融合激光雷达点云与惯性测量单元(IMU)数据,实现高精度、低漂移的位姿估计。其核心原理在于利用IMU的高频数据预测运动,并用激光雷达的精确测距信息进行校正与优化,从而在快速运动或特征缺失场景下保持鲁棒性。这项技术的工程价值在于为自动驾驶、移动机器人及三维重建提供了可靠的前端里程计解决方案。针对Livox MID-360固态雷达与FAST-LIO2这一热门组合,其应用场景广泛覆盖了室内外导航与地图构建。本文聚焦于**驱动部署

weixin_29997523的博客 448

Java多线程案例【阻塞队列】

阻塞队列特性:一.安全性二.产生阻塞效果:阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型.

积跬步以至千里 1776

从0到1:用Claude 3.7十日拿下教育部课题申报书(附可修改LaTeX模板)

🟣"作为教育部人文社科项目评审专家,请根据近三年立项数据分析,为[您的学科方向]领域推荐5个具有创新性且符合'十四五'规划的重点选题,要求选题体现交叉学科特征,并标注潜在理论突破点"🟣"根据前期研究基础,创建包含4个研究模块、12个关键节点的三维技术路线图,要求体现实验仿真-实证研究-理论建模的螺旋递进关系,并标注每个节点的预期成果形态"🟣"现在切换至教育部学科评审组组长模式,从选题价值、创新程度、研究可行性三个维度对本申报书进行盲审,生成包含9个扣分风险点的诊断报告及修改优先级排序"

智能AI让学术科研更容易 546

K230目标识别数字提问:模型只能识别近距离的目标,稍远一点就无法正确检测...如何解决?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 1901

Java多线程案例——阻塞队列

Java多线程案例——阻塞队列

如风暖阳的博客 1834

探索CompletableFuture:高效异步编程的利器

随着现代软件系统的复杂性和用户需求的多样化,异步编程成为了提升系统性能和响应速度的重要手段。在Java领域,CompletableFuture作为Java 8引入的新特性,提供了强大的异步编程能力,极大地简化了多线程和并发任务的处理。本文将深入探讨CompletableFuture的基本功能和使用方法,介绍如何利用其提升程序的并发性能和代码的可维护性。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 170万+

【Java多线程案例】实现阻塞队列

简单介绍了Java标准库提供的阻塞队列(BlockingQueue)的使用方法,并逐步模拟实现了自定义阻塞队列的数据结构,包括实现普通队列、加锁实现线程安全、引入阻塞功能,并基于自定义阻塞队列实现消费者-生产者模型

weixin_62533201的博客 2792

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Java 多线程案例-阻塞队列

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

m0_64986499的博客 344

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

【Java多线程】案例(2):阻塞队列

阻塞队列(Blocking Queue)是一种特殊的队列数据结构,具有阻塞的特性。它主要用于多线程编程中,提供了线程安全的数据交换的方法。具有的特性:当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素.当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素.阻塞队列的一个典型应用场景就是“生产者消费者模型”。这是一种非常典型的开发模型。

StuLearn-LJC的博客 1000

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

【Java并发编程三】多线程案例(手撕单例模式,阻塞队列,定时器,线程池)

定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.上述代码还可以进行改进,加锁和解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.懒汉模式在类加载的时候不创建实例,第一次使用的时候才创建实例。

2203_75565612的博客 1862

Java多线程基础-9:代码案例之阻塞队列

介绍一下Java多线程编程中的代码案例:阻塞队列。包括Java标准库BlockingQueue接口和该接口的代码实现,生产者-消费者模型以及对SynchronousQueue的补充说明。

wyd_333的博客 1425

【Java多线程案例】使用阻塞队列实现生产者消费者模型

本期讲解:阻塞队列之生产者消费者模型,模拟实现生产者消费者模型。

我的主页 3270

JAVA进阶 THREAD学习09 多线程案例--阻塞队列

阻塞队列会让需要操作的数据处在一个较为稳定的范围内(队列的长度范围内),控制数据的操作速率,解决了问题1和2.而且阻塞队列会将所需要操作的数据进行排序,保证了访问次序,解决了问题3.包饺子的人不需要知道是谁擀的皮,也不需要知道擀皮的人是用擀面杖擀、用罐头擀或者是直接去超市买的,他只需要把盘子里的皮过来包就行了。擀皮的人不需要知道下一个是谁用他擀的皮去包饺子,也不需要知道包的饺子是什么样子的,他只需要擀好了就往盘子上放就好了。就好比大家一起包饺子,一拨人擀皮,一拨人包。4个基本内容:存、移、变、释。

Roger_1900的博客 946

【Java系列】多线程案例学习——基于阻塞队列实现生产者消费者模型

【Java系列】多线程案例学习——基于阻塞队列实现生产者消费者模型

m0_74352571的博客 2884

Java多线程案例——阻塞队列(生产者消费者模型)

阻塞队列(生产者消费者模型)

小小码农 1166

Java 多线程案例之生产者-消费者模型以及阻塞队列的实现

当队列满时,会阻塞执行入队列操作的线程,直到队列不为满,直至队列不满;当队列为空时,会阻塞执行出队列操作的线程,直至队列不为空。阻塞队列用在多线程的场景下,因此阻塞队列使用了锁机制来保证同步。生产者-消费者模型解决的是多线程或多进程之间数据共享和同步的问题,而阻塞队列作为一种数据结构,则为实现生产者-消费者模型提供了方便的工具。通过使用阻塞队列,可以减少开发人员处理并发问题的复杂性,同时提高代码的可维护性和可读性。

weixin_63025863的博客 765
上一篇: 阿里藏经阁天花板:高性能Java架构核心原理手册,一定要偷偷看
下一篇: Java 消费者是如何管理TCP连接的?
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值