JavaEE多线程-阻塞队列

news2025/1/15 20:08:01

目录

  • 一、认识阻塞队列
    • 1.1 什么是阻塞队列?
    • 1.2 生产者消费者模型
    • 1.3 标准库中的阻塞队列类
  • 二、循环队列实现简单阻塞队列
    • 2.1 实现循环队列
    • 2.2 阻塞队列实现

一、认识阻塞队列

1.1 什么是阻塞队列?

阻塞队列:从名字可以看出,他也是队列的一种,那么他肯定是一个先进先出(FIFO)的数据结构。与普通队列不同的是,他支持两个附加操作,即阻塞添加和阻塞删除方法。

阻塞队列本质上还是一种队列, 和普通队列一样, 遵循先进先出, 后进后出的规则, 但阻塞队例相比于普通队列的特殊之处在于阻塞队列的阻塞功能, 主要基于多线程使用.

1.如果队列为空, 执行出队列操作, 就会使线程陷入阻塞, 阻塞到另一个线程往队列里添加元素(队列不空)为止.
2.如果队列满了,执行入队列操作, 也会使线程阻塞, 阻塞到另一个线程从队列取走元素位置(队列不满)为止.

1.2 生产者消费者模型

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

这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。

在这里插入图片描述
生产者消费者模型能够给程序带来两个非常重要的好处, 一是可以实现实现了发送方和接收方之间的 “解耦” , 二是可以 “削峰填谷” , 保证系统的稳定性, 具体理解如下:

1.3 标准库中的阻塞队列类

Java标准库也提供了阻塞队列的标准类, 常用的有下面几个:

  • ArrayBlockingQueue : 基于数组实现界阻塞队列
  • LinkedBlockingQueue : 基于链表实现的有界阻塞队列
  • PriorityBlockingQueue : 带有优先级(堆)的无界阻塞队列
  • BlockingQueue接口 : 上面的类实现了该接口
  • 根据插入和取出两种类型的操作,具体分为下面一些类型:
    在这里插入图片描述

  • 抛出异常是指当队列满时,再次插入会抛出异常(如果队列未满,插入返回值未true);
  • 返回布尔是指当队列满时,再次插入会返回false;
  • 阻塞是指当队列满时,再次插入会被阻塞,直到队列取出一个元素,才能插入。
  • 超时是指当一个时限过后,才会插入或者取出。
  • public class Test {
        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) {
                       throw new RuntimeException(e);
                   }
               }
            });
            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) {
                        throw new RuntimeException(e);
                    }
                }
            });
            producer.start();
        }
    }
    
    

    二、循环队列实现简单阻塞队列

    2.1 实现循环队列

    //普通的循环队列
    class MyBlockingQueue {
        //存放元素的数数组
        private int[] items = new int[1000];
        //队头指针
        private int head = 0;
        //队尾指针
        private int tail = 0;
        //记录队列元素的个数
        private int size = 0;
    
        //入队操作
        public void put (int val) {
            if (size == items.length) {
                //队列满了
                return;
            }
            items[tail++] = val;
            //等价于 tail %= items.length
            if (tail >= items.length) {
                tail = 0;
            }
            size++;
        }
    
        //出队操作
        public Integer take() {
            int resulet = 0;
            if (size == 0) {
                //队列空了
                return null;
            }
            resulet = items[head++];
            //等价于 head %= elem.length
            if (head >= items.length) {
                head = 0;
            }
            size--;
            return resulet;
        }
    }
    
    

    2.2 阻塞队列实现

    考虑线程安全问题,循环队列中的take和put方法都有写操作,直接加锁即可。

    //线程安全的循环队列
    class MyBlockingQueue {
        //存放元素的数数组
        private int[] items = new int[1000];
        //队头指针
        private int head = 0;
        //队尾指针
        private int tail = 0;
        //记录队列元素的个数
        private int size = 0;
    
        //入队操作
        public void put (int val) {
            synchronized (this) {
                if (size == items.length) {
                    //队列满了
                    return;
                }
                items[tail++] = val;
                //等价于 tail %= items.length
                if (tail >= items.length) {
                    tail = 0;
                }
                size++;
            }
        }
    
        //出队操作
        public Integer take() {
            int resulet = 0;
            synchronized (this) {
                if (size == 0) {
                    //队列空了
                    return null;
                }
                resulet = items[head++];
                //等价于 head %= elem.length
                if (head >= items.length) {
                    head = 0;
                }
                size--;
                return resulet;
            }
        }
    }
    
    

    实现阻塞效果,主要使用wait和notify实现线程的阻塞等待
    入队时, 队列满了需要使用wait方法使线程阻塞, 直到有元素出队队列不满了再使用notify通知线程执行.
    出队时, 队列为空也需要使用wait方法使线程阻塞, 直到有新元素入队再使用notify通知线程执行.

    class MyBlockingQueue {
        //存放元素的数数组
        private int[] items = new int[1000];
        //队头指针
        private int head = 0;
        //队尾指针
        private int tail = 0;
        //记录队列元素的个数
        private int size = 0;
    
        //入队操作
        public void put (int val) throws InterruptedException {
            synchronized (this) {
                if (size == items.length) {
                    //队列满了,阻塞等待
                    this.wait();
                }
                items[tail++] = val;
                //等价于 tail %= items.length
                if (tail >= items.length) {
                    tail = 0;
                }
                size++;
                //唤醒因队列空造成的阻塞wait
                this.notify();
            }
        }
    
        //出队操作
        public Integer take() throws InterruptedException {
            int resulet = 0;
            synchronized (this) {
                if (size == 0) {
                    //队列空了,阻塞等待
                    this.wait();
                }
                resulet = items[head++];
                //等价于 head %= elem.length
                if (head >= items.length) {
                    head = 0;
                }
                size--;
                //唤醒因队列满造成的阻塞wait
                this.notify();
                return resulet;
            }
        }
    }
    
    

    思考:当代码中当wait被唤醒的时候,此时的if条件一定就不成立了吗?

    为了稳妥起见,最好的办法就是wait唤醒之后再判断一下条件是否满足。

    //出队部分
    while (size == items.length) {
        //队列满了,阻塞等待
        this.wait();
    }
    
    //入队部分
    while (size == 0) {
        //队列空了,阻塞等待
        this.wait();
    }
    
    

    我们创建两个线程分别是消费者线程customer和生产者线程producer, 生产者生产数字, 消费者消费数字, 为了让执行结果中的阻塞效果明显一些, 我们可以使用sleep方法来控制一下生产者/消费者的生产/消费的频率, 这里我们让开始时生产的速度快一些, 消费的速度慢一些。

    class MyBlockingQueue {
        //存放元素的数数组
        private int[] items = new int[1000];
        //队头指针
        private int head = 0;
        //队尾指针
        private int tail = 0;
        //记录队列元素的个数
        private int size = 0;
    
        //入队操作
        public void put (int val) throws InterruptedException {
            synchronized (this) {
                while (size == items.length) {
                    //队列满了,阻塞等待
                    this.wait();
                }
                items[tail++] = val;
                //等价于 tail %= items.length
                if (tail >= items.length) {
                    tail = 0;
                }
                size++;
                //唤醒因队列空造成的阻塞wait
                this.notify();
            }
        }
    
        //出队操作
        public Integer take() throws InterruptedException {
            int resulet = 0;
            synchronized (this) {
                while (size == 0) {
                    //队列空了,阻塞等待
                    this.wait();
                }
                resulet = items[head++];
                //等价于 head %= elem.length
                while (head >= items.length) {
                    head = 0;
                }
                size--;
                //唤醒因队列满造成的阻塞wait
                this.notify();
                return resulet;
            }
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            //消费线程
            MyBlockingQueue queue = new MyBlockingQueue();
            Thread customer = new Thread(() -> {
                while(true) {
                    try {
                        int result = queue.take();
                        System.out.println("消费元素: " + result);
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            customer.start();
            //生产线程
            Thread producer = new Thread(() -> {
                int count = 0;
                while (true) {
                    try {
                        queue.put(count);
                        System.out.println("生产元素: " + count);
                        count++;
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            producer.start();
        }
    }
    
    

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

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

相关文章

简明Java讲义 2:数据类型和运算符

目录 1、安装IDE编辑器 2、关键字和保留字 3、标识符 4、分隔符 5、数据类型 6、基本类型的数据类型转换 7、表达式类型的自动提升 8、变量 9、运算符 10、运算符的优先级 1、安装IDE编辑器 在开始内容之前&#xff0c;先下载IDE&#xff0c;可以是Eclipse或STS&…

Python函数(函数定义、函数调用)用法详解

Python 中函数的应用非常广泛&#xff0c;前面章节中我们已经接触过多个函数&#xff0c;比如 input() 、print()、range()、len() 函数等等&#xff0c;这些都是 Python 的内置函数&#xff0c;可以直接使用。除了可以直接使用的内置函数外&#xff0c;Python 还支持自定义函数…

LeetCode刷题模版:201 - 210

目录 简介201. 数字范围按位与202. 快乐数203. 移除链表元素204. 计数质数205. 同构字符串206. 反转链表207. 课程表【未实现】208. 实现 Trie (前缀树)209. 长度最小的子数组210. 课程表 II【未实现】结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您…

LeetCode[1319]连通网络的操作次数

难度&#xff1a;中等题目&#xff1a;用以太网线缆将 n台计算机连接成一个网络&#xff0c;计算机的编号从 0到 n-1。线缆用 connections表示&#xff0c;其中 connections[i] [a, b]连接了计算机 a和 b。网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其…

(十六)异步编程

CompletableFuture在Java8中推出&#xff0c;Java8中的异步编程就是依靠此类。几种任务接口四种任务无参数有一个参数有两个参数无返回值RunnableConsumerBiConsumer有返回值SupplierFunctionBiFunctionCompletionStage接口这个类中定义的许多能够链式调用的方法和组合方法时是…

Unity3DVR开发—— XRInteractionToolkit(PicoNeo3)

目录 一、开发前的准备 二、基础配置 三、Pico项目配置 四、添加基础功能 一、开发前的准备 1、为了方便开发&#xff0c;先在Pico开发者平台里下载预览工具 Pico开发者平台https://developer-global.pico-interactive.com/sdk?deviceId1&platformId1&itemId17 2、…

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

【哈希表】理论基础1 哈希表2 哈希函数3 哈希碰撞3.1 拉链法3.2 线性探测法4 常见的三种哈希结构5 总结1 哈希表 哈希表 Hash table &#xff08;一些书籍翻译为散列表&#xff09; 哈希表是根据关键码的值而直接进行访问的数据结构。 直白来讲其实数组就是一张哈希表。 哈希表…

用1行Python代码识别增值税发票,YYDS

大家好&#xff0c;这里是程序员晚枫。 录入发票是一件繁琐的工作&#xff0c;如果可以自动识别并且录入系统&#xff0c;那可真是太好了。 今天我们就来学习一下&#xff0c;如何自动识别增值税发票并且录入系统~ 识别发票 识别发票的代码最简单&#xff0c;只需要1行代码…

CSS的总结

从HTML被发明开始&#xff0c;样式就以各种形式存在。不同的浏览器结合它们各自的样式语言为用户提供页面效果的控制。最初的HTML只包含很少的显示属性。 随着HTML的成长&#xff0c;为了满足页面设计者的要求&#xff0c;HTML添加了很多显示功能。但是随着这些功能的增加&…

ISIS的路由器级别level-1、level-2、level-1-2,报文格式

2.1.0 ISIS的路由器级别level-1、level-2、level-1-2&#xff0c;报文格式 通过该文章了解ISIS的路由器级别类型、级别之间建立的邻接关系、各级别的作用、ISIS报文的结构。 ISIS路由器级别 Level-1 level-1路由器又称L1路由器&#xff0c;是一种ISIS区域内部路由&#xff0c…

6、运算符

目录 一、赋值运算符 二、算数运算符 三、自增和自减运算符 四、比较运算符 五、逻辑运算符 六、位运算符 1. “按位与”运算 2. “按位或”运算 3. “按位取反”运算 4. “按位异或”运算 5. 移位操作 七、三元运算符 八、运算符优先级 一、赋值运算符 赋值运算…

[Android开发基础1] 五大常用界面布局

文章目录 一、线性布局 二、相对布局 三、帧布局 四、表格布局 五、约束布局 总结 一、线性布局 线性布局&#xff08;LinearLayout&#xff09;主要以水平或垂直方式来显示界面中的控件。当控件水平排列时&#xff0c;显示顺序依次为从左到右&#xff0c;当控件垂直排列时…

29/365 java 网络通信 IP InetAddress

1.网络通信&#xff1a; 如何定位到一台主机&#xff1f; IP地址 定位主机&#xff0c; 端口号定位到具体的应用程序 如何在主机之间通信&#xff08;传输数据&#xff09;&#xff1f; 网络通信协议 2.IP地址分类 IPv4: 32位 IPv6地址&#xff1a;128位 IPv6地址使用以冒号…

初学python100例-案例37 合并排序列表 少儿编程python编程实例讲解

目录 python合并排序列表 一、题目要求 1、编程实现 2、输入输出

C语言形参和实参的区别

如果把函数比喻成一台机器&#xff0c;那么参数就是原材料&#xff0c;返回值就是最终产品&#xff1b;从一定程度上讲&#xff0c;函数的作用就是根据不同的参数产生不同的返回值。这一节我们先来讲解C语言函数的参数&#xff0c;下一节再讲解C语言函数的返回值。C语言函数的参…

浅谈多任务学习

目录 一、前言及定义 二、多任务学习&#xff08;MTL&#xff09;的两种方法 2.1 参数的硬共享机制&#xff08;hard parameter sharing&#xff09; 2.2 参数的软共享机制&#xff08;soft parameter sharing&#xff09; 三、多任务学习模型 3.1 MT-DNN 3.2 ERNIE 2.0…

数学建模学习笔记(9)多元线性回归分析(非常详细)

多元线性回归分析1.回归分析的地位、任务和分类2.数据的分类3.对线性的理解、系数的解释和内生性4.取对数预处理、虚拟变量和交互效应5.使用Stata进行多元线性回归分析6.异方差7.多重共线性8.逐步回归法1.回归分析的地位、任务和分类 回归分析的地位&#xff1a;数据分析中最基…

cclow 面试心得

开源ccflow学习的一些心得目录概述需求&#xff1a;设计思路实现思路分析1.心得参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge …

JavaEE-文件和IO(一)

目录一、文件1.1 认识文件1.2 树型结构组织和目录1.3 文件路径二、Java中操作文件2.1 文件系统相关的操作一、文件 1.1 认识文件 平时说的文件一般都是指存储再硬盘上的普通文件&#xff0c;形如txt&#xff0c;jpg&#xff0c;MP4&#xff0c;rar等这些文件都可以认为是普通…

Java集合常见面试题(四)

Map 接口 HashMap 的底层实现 JDK1.8 之前 JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列&#xff0c;数组是 HashMap 的主体&#xff0c;链表则是主要为了解决哈希冲突而存在的。 HashMap 通过 key 的 hashcode 经过扰动函数&#xff08;hash函数&…