Juc04_阻塞队列概述、方法、实现类、Linked和Array区别、注意事项

2023-10-30

①. 什么是阻塞队列

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

  • ②. 线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素。而在这一系列操作必须符合以下规定:

  1. 阻塞添加:当阻塞队列是满时,往队列里添加元素的操作将被阻塞
  2. 阻塞移除:当阻塞队列是空时,从队列中获取元素/删除元素的操作将被阻塞

在这里插入图片描述

  • ③. 现有三个角色:顾客,休息区,银行办理窗口。Thread1为顾客,BlockingQueue为休息区,Thread2为银行办理窗口
  1. 正常情况下,一个银行办理窗口同一时间只能对接一个顾客
  2. 恰巧今天办理的顾客有3个人,另外2个顾客怎么办,你总不至于给人家说不办了,快回家吧
  3. 而正确的做法是你可以让这两个人在休息区等候,等银行窗口空闲了,然后去办理
  • ④. 阻塞队列的好处:阻塞队列不用手动控制什么时候该被阻塞,什么时候该被唤醒,简化了操作

②. BlockingQueue的主要方法

  • ①. BlockingQueue提供的部分方法:
public interface BlockingQueue<E> extends Queue<E> {
   
    boolean add(E e);

    boolean offer(E e);

    void put(E e) throws InterruptedException;

    boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException;
        
    E take() throws InterruptedException;

    E poll(long timeout, TimeUnit unit)throws InterruptedException;
        
    int remainingCapacity();

    boolean remove(Object o);

    public boolean contains(Object o);

    int drainTo(Collection<? super E> c);

    int drainTo(Collection<? super E> c, int maxElements);
}
  • ②. 根据插入和取出两种类型的操作,具体分为下面一些类型:
  1. 抛出异常是指当队列满时,再次插入会抛出异常如果队列未满,插入返回值未true
  2. 返回布尔是指当队列满时,再次插入会返回false
  3. 阻塞是指当队列满时,再次插入会被阻塞,直到队列取出一个元素,才能插入
  4. 超时是指当一个时限过后,才会插入或者取出

在这里插入图片描述

  • ③. 生产 - add、offer、put这3个方法都是往队列尾部添加元素,区别如下:
  1. add:不会阻塞,添加成功时返回true,不响应中断,当队列已满导致添加失败时抛出IllegalStateException
  2. offer:不会阻塞,添加成功时返回true,因队列已满导致添加失败时返回false,不响应中断
  3. put:会阻塞会响应中断
  • ④. 消费 - take、poll方法能获取队列头部第1个元素,区别如下:
  1. 会响应中断,会一直阻塞直到取得元素或当前线程中断
  2. 会响应中断,会阻塞,阻塞时间参照方法里参数timeout.timeUnit,当阻塞时间到了还没取得元素会返回null
  • ⑤. add方法代码以及结果
public class BlockingQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
        System.out.println("--------以下为add的相关操作---------");
        addRemoveTest(blockingQueue);
    }

    public static void addRemoveTest(BlockingQueue<String> blockingQueue) {
        System.out.println("添加状态+\t"+blockingQueue.add("1"));
        System.out.println("添加状态+\t"+blockingQueue.add("2"));
        System.out.println("添加状态+\t"+blockingQueue.add("3"));
//        System.out.println("添加状态+\t"+blockingQueue.add("4"));
        System.out.println("队首元素+\t"+blockingQueue.element());
        System.out.println("删除元素+\t"+blockingQueue.remove());
        System.out.println("队首元素+\t"+blockingQueue.element());
        System.out.println("删除元素+\t"+blockingQueue.remove());
        System.out.println("队首元素+\t"+blockingQueue.element());
        System.out.println("删除元素+\t"+blockingQueue.remove());
//        System.out.println("队首元素+\t"+blockingQueue.element());
//        System.out.println("删除元素+\t"+blockingQueue.remove(blockingQueue.element()));
    }
}
// 未打开注释代码输出如下:
--------以下为add的相关操作---------
添加状态+	true
添加状态+	true
添加状态+	true
队首元素+	1
删除元素+	1
队首元素+	2
删除元素+	2
队首元素+	3
删除元素+	3
// 当队列已满,继续添加元素时打开注释代码,输出如下:
--------以下为add的相关操作---------
添加状态+	true
添加状态+	true
添加状态+	true
Exception in thread "main" java.lang.IllegalStateException: Queue full
	at java.util.AbstractQueue.add(AbstractQueue.java:98)
  • ⑥. offer方法代码
public class BlockingQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
        System.out.println("--------以下为offer的相关操作---------");
       offerPollTest(blockingQueue);
    }

    private static void offerPollTest(BlockingQueue<String> blockingQueue) {
        System.out.println("添加状态+\t"+blockingQueue.offer("1"));
        System.out.println("添加状态+\t"+blockingQueue.offer("2"));
        System.out.println("添加状态+\t"+blockingQueue.offer("3"));
        System.out.println("添加状态+\t"+blockingQueue.offer("4"));
        System.out.println("队首元素+\t"+blockingQueue.peek());
        System.out.println("删除元素+\t"+blockingQueue.poll());
        System.out.println("队首元素+\t"+blockingQueue.peek());
        System.out.println("删除元素+\t"+blockingQueue.poll());
        System.out.println("队首元素+\t"+blockingQueue.peek());
        System.out.println("删除元素+\t"+blockingQueue.poll());
        System.out.println("删除元素+\t"+blockingQueue.poll());
    }
}
// 输出如下:
--------以下为offer的相关操作---------
添加状态+	true
添加状态+	true
添加状态+	true
添加状态+	false
队首元素+	1
删除元素+	1
队首元素+	2
删除元素+	2
队首元素+	3
删除元素+	3
删除元素+	null
// 注意:当队列没有元素的时候使用poll,返回null
  • ⑦. put方法代码
public class BlockingQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
        System.out.println("--------以下为put的相关操作---------");
        putTakeTest(blockingQueue);
    }

     private static void putTakeTest(BlockingQueue<String> blockingQueue) {
        try{
            blockingQueue.put("1");
            blockingQueue.put("2");
            blockingQueue.put("3");
//            blockingQueue.put("4");
            System.out.println("删除元素+\t"+blockingQueue.take());
            blockingQueue.put("4");
            System.out.println("删除元素+\t"+blockingQueue.take());
            System.out.println("删除元素+\t"+blockingQueue.take());
            System.out.println("删除元素+\t"+blockingQueue.take());
        }catch(Exception e){
            e.getStackTrace();
        }
    }
}
// 打开注释代码输出如下:(程序未停止)
--------以下为put的相关操作---------
// 未打开注释代码输出如下:
--------以下为put的相关操作---------
删除元素+	1
删除元素+	2
删除元素+	3
删除元素+	4

③. BlockingQueue的实现类

  • ①. 从整体架构图上来看,BlockingQueue是实现了Queue接口,而Queue是属于Collection接口下的派生类

在这里插入图片描述

  • ②. ArrayBlockingQueue: 由数组结构组成的有界阻塞队列

  • ③. LinkedBlockingQueue: 由链表结构组成的有界(但大小默认值Integer>MAX_VAL UE)阻塞队列
    需要注意的是LinkedBlockingQueue虽然是有界的,但有个巨坑,其默认大小是IntegerMAX_VALUE,高达21亿,一般情况下内存早爆了在线程池的ThreadPoolExecutor有体现

  • ④. SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列

  1. SynchronousQueue没有容量,与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue
  2. 每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然
public class SynchronusQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> synchronusQueue = new SynchronousQueue<>();

        new Thread(() ->{
            try{
                System.out.println(Thread.currentThread().getName()+"\t put 1");
                synchronusQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"\t put 2");
                synchronusQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"\t put 3");
                synchronusQueue.put("3");
            }catch(Exception e){
                e.getStackTrace();
            }
        },"Prod").start();

        new Thread(() ->{
            try {
                try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t take "+synchronusQueue.take());
                try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t take "+synchronusQueue.take());
                try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t take"+synchronusQueue.take());
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"Cons").start();
    }
// 输出如下:有时间间隔
Prod	 put 1
Cons	 take 1
Prod	 put 2
Cons	 take 2
Prod	 put 3
Cons	 take3

  • ⑤. PriorityBlockingQueue:支持优先级排序的无界阻塞队列

  • ⑥. LinkedTransferQueue:由链表结构组成的无界阻塞队列

  • ⑦. LinkedBlockingDeque:由了解结构组成的双向阻塞队列

④. Linked和Array区别

  • ①. 队列大小不同:
  1. arrayBlockingQueue在初始化的时候,必须指定队列的大小
  2. 而LinkedBlockingQueue在初始化的时候,如果你没有指定大小,则会默认Integer.MAX_VALUE,是一个很大的值,这样就会导致数据在一个不可控范围,一旦添加速度远大于移除的速度时,可能会有内存泄漏的风险
  • ②. 底层实现不同
  1. arrayBlockingQueue的底层是一个数组
  2. LinkedBlockingQueue底层是一个链表结构
  3. 官方文档介绍中,LinkedBlockingQueue的吞吐行是高于arrayBlockingQueue;但是在添加或移除元素中,LinkedBlockingQueue则会生成一个额外的Node对象,对GC可能存在影响
  • ④. 为什么说LinkedBlockingQueue的吞吐性是高于arrayBlockingQueue?
    吞吐性能强是因为有两个锁,试想一下,Array里面使用的是一个锁,不管put还是take行为,都可能被这个锁卡住,而Linked里面put和take是两个锁,put只会被put行为卡住,而不会被take卡住,因此吞吐性能自然强于Array。 而“less predictable performance”这个也是显而易见的,Array采用的时固定内存,而Linked采用的时动态内存,无论是分配内存还是释放内存甚至GC动态内存的性能自然都会比固定内存要差

  • ⑤. 锁机制不一样
    arrayBlockingQueue使用的一个锁来控制,LinkedBlockingQueue使用了2个锁来控制,一个名为putLock,另一个是takeLock,但是锁的本质都是ReentrantLock

⑤. 不推荐使用快捷的线程池

  • ①. 我们需要根据自己的场景、并发情况来评估线程池的几个核心参数,包括核心线程数、最大线程数、线程回收策略、工作队列的类型,以及拒绝策略,确保线程池的工作行为符合需求,一般都需要设置有界的工作队列和可控的线程数

  • ②. 任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数量暴增、线程死锁、线程占用大量 CPU、线程执行出现异常等问题时,我们往往会抓取线程栈。此时,有意义的线程名称,就可以方便我们定位问题

  • ③. newFixedThreadPool方法的源码不难发现,线程池的工作队列直接new了一个LinkedBlockingQueue,而默认构造方法的 LinkedBlockingQueue是一个Integer.MAX_VALUE 长度的队列,可以认为是无界的

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    ...


    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
...
}
  • ④. 翻看newCachedThreadPool的源码可以看到,这种线程池的最大线程数是Integer.MAX_VALUE,可以认为是没有上限的,而其工作队列 SynchronousQueue是一个没有存储空间的阻塞队列。这意味着,只要有请求到来,就必须找到一条工作线程来处理,如果当前没有空闲的线程就再创建一条新的
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
  • ⑤. 线程池的OOM问题,可能是队列满造成的(LinkedBlockingQueue),也可能是线程太多造成的(SynchronousQueue)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Juc04_阻塞队列概述、方法、实现类、Linked和Array区别、注意事项 的相关文章

随机推荐

  • 51单片机入门教程(2)——实现流水灯

    51单片机入门教程 2 实现流水灯 一 搭建流水灯电路 二 流水灯程序 2 1 延时程序 2 2 延时函数 2 3 按字节寻址 2 4 逻辑移位 2 5 条件判断 一 搭建流水灯电路 在Proteus中搭建流水灯电路如图 二 流水灯程序 我
  • 为什么我这么累?

    本月十六日 我和媳妇喜爱的歌手姚贝娜去世了 你是个好歌手 真正的热爱音乐 希望你在天堂里面开心的唱歌 我是个参加工作不久的小程序员 过着家 地铁 公司三点一线的生活 每天早上七点二十出门 地铁上一个多小时 8点五十到公司 下午六点下班 一个
  • 【FreeRTOS 信号量】互斥信号量

    互斥信号量与二值信号量类似 但是互斥信号量可以解决二值信号量出现的优先级翻转问题 解决办法就是优先级继承 普通互斥信号量创建及运行 参阅安富莱电子demo 互斥信号量句柄 static SemaphoreHandle t xMutex NU
  • 卷积神经网络中卷积层、池化层、全连接层的作用

    1 卷积层的作用 卷积层的作用是提取输入图片中的信息 这些信息被称为图像特征 这些特征是由图像中的每个像素通过组合或者独立的方式所体现 比如图片的纹理特征 颜色特征 比如下面这张图片 蓝色框框住的地方就是脸部特征 这些特征其实是由一个个像素
  • 消息鉴别码的原理与应用

    消息鉴别码可以确认自己收到的消息是否就是发送者的本意 也就是说 使用消息鉴别码可以判断消息是否被篡改 以及是否有人伪装成发送者发送该消息 消息鉴别码实现鉴别的原理是 用公开函数和密钥产生一个固定长度的值作为认证标识 用这个标识鉴别消息的完整
  • uml活动图

    活动图与交互图的区别 交互图强调的是对象到对象的控制流 而活动图则强调的是从活动到活动的控制流 初始节点和活动终点 用一个实心圆表示初始节点 用一个圆圈内加一个实心圆来表示活动终点 活动节点 是活动图中最主要的元素之一 它用来表示一个活动
  • 基于CNN的人脸表情识别系统

    基于CNN的人脸表情识别系统 主要功能 1 图片识别 可以通过上传本地图片 进行表情识别 2 拍照识别 点击拍照识别按钮 可以调用摄像头实现拍照 并进 行表情识别 实现原理 1 表情库的建立 fer2013人脸数据集 可以从kaggle网站
  • 上/下采样的方法

    下采样方式一般使用池化 pooling 操作 上采样 upsampling 的三种方式 插值法 Interpolation 插值就是在周围像素色彩的基础上用数学公式计算补充插入像素点的色彩 但必须注意的是插值并不能增加图像信息 如双线性插值
  • 数字电子技术-逻辑门电路

    文章目录 一 理想开关 二 基本CMOS逻辑门电路 2 1 MOS管开关特性 2 2 CMOS反相器 2 3 常用CMOS逻辑门电路 2 4 CMOS传输门 2 5 CMOS漏极开路门和三态输出门电路 2 6 CMOS逻辑门电路的重要参数
  • 写一个GitHub图床

    刚刚完成一个作业 涉及到图片的上传服务 因为自己经常会有一些图片管理的需求 七牛云 阿里云的oos存储又是付费的 所以自己根据GitHub的官方API搭建一个自己的图床服务 以便后续自己开发使用 参考地址 GitHubAPI import
  • GoLang - colly爬虫框架

    大家好 我是TheWeiJun 很高兴又和大家见面了 国庆假期马上就要结束了 在国庆假期里小编看了下colly框架 故这篇文章中将提到colly的使用及分析 欢迎各位读者多多阅读与交流 特别声明 本公众号文章只作为学术研究 不作为其它不法用
  • 实践安装minio

    一 下载安装文件 1 在home目录下创建minio文件夹 mkdir home minio 2 进入 home minio 文件夹 cd home minio 3 下载文件 此处下载比较慢 建议手动下载 然后上传到目录中 wget htt
  • springboot集成nacos配置中心踩坑记录

    目录 1 下载安装 准备 下载地址 可以尝试选择最新版本 解压 2 添加数据库配置 特别注意 3 登陆nacos添加配置 4 springboot项目中获取配置中心配置 1 引入依赖 2 创建bootstrap yaml配置文件 3 启动注
  • 在嵌入式板子ARMv7 上利用neon对彩色图转换为灰度图进行加速

    RGB转GRAY公式如下 本实验通过对一张1920 1080 分辨率大小RGB彩色图进行灰度图转换测试耗时时间 测试条件为 嵌入式开发板ssc9381g A7 通过四种转换方式进行耗时对比 结果如下 方式1 通过opencv 库函数cvtC
  • 线程池异常java.util.concurrent.RejectedExecutionException

    问题及代码 使用 ThreadPoolExecutor 自定义线程池时 出现java util concurrent RejectedExecutionException的错误信息 源代码如下 public static void main
  • JVM系列(二) Java 堆内存分析

    Java 堆内存分析 堆是GC 垃圾收集器 执行垃圾回收的重点区域 所以今天我们着重讲下堆内存 自己的项目 如果出现OOM或者出现内存泄露 一定是出在堆内存上 因为堆是JVM中最大的一块内存空间 所有线程共享Java堆 物理上不连续的逻辑上
  • Vue/cli 3.0中使用百度地图api

    1 申请百度地图ak ak会在引入百度js的时候用到 你可以点击这里申请一个唯一ak 2 引入百度地图js 在public index html里面添加下面代码 注意把 你的ak 替换成你申请的ak 复制代码 3 配置webpack 由于c
  • 深度学习激活函数和权值初始化

    激活函数选择总结 尽量选择ReLU函数或者Leakly ReLU函数 相对于Sigmoid tanh ReLU函数或者Leakly ReLU函数会让梯度流更加顺畅 训练过程收敛得更快 权值初始化总结 好的初始化方法可以防止前向传播过程中的信
  • VB6反编译详解

    标 题 VB6反编译详解 一 作 者 kenmark时 间 2006 07 09 16 59 链 接 http bbs pediy com showthread php threadid 28715 详细信息 VB6反编译详解 by Ken
  • Juc04_阻塞队列概述、方法、实现类、Linked和Array区别、注意事项

    文章目录 什么是阻塞队列 BlockingQueue的主要方法 BlockingQueue的实现类 Linked和Array区别 不推荐使用快捷的线程池 什么是阻塞队列 阻塞队列 从名字可以看出 它也是队列的一种 那么它肯定是一个先进先出F