Java阻塞队列中的BlockingQueue接口详解

 更新时间:2023年09月27日 10:35:04   作者:WYSCODER  
这篇文章主要介绍了Java阻塞队列中的BlockingQueue接口详解,对于Queue而言,BlockingQueue是主要的线程安全的版本,具有阻塞功能,可以允许添加、删除元素被阻塞,直到成功为止,BlockingQueue相对于Queue而言增加了两个方法put、take元素,需要的朋友可以参考下

BlockingQueue

对于Queue而言,BlockingQueue是主要的线程安全的版本,具有阻塞功能,可以允许添加、删除元素被阻塞,直到成功为止,BlockingQueue相对于Queue而言增加了两个方法put、take元素。

在这里插入图片描述

BlockingQueue接口

属于并发容器中的接口,在java.util.concurrent包路径下

  • BlockingQueue不接受null元素,加入尝试通过add、put、offer等添加一个null元素时,某些实现上会抛出nullpointExeception问题。
  • BlockingQueue是可以指定容量,如果给定的数据超过给定容量,便无法添加元素,如果没有指定容量约束,最大大小是Interger.MAX_VALUE值
  • BlockingQueue实现类主要用于生产者-消费者队列,另支持Collection接口。
  • BlockingQueue实现了线程安全,所有排队方法都可以使用内部锁或者其他并发控制形式来达到线程安全的目的。

三个主要实现类介绍:

  • ArrayBlockingQueue:有界阻塞队列
  • LinkedBlockingQueue:无界阻塞队列
  • SynchronousQueue: 同步队列

ArrayBlockingQueue:有界队列

ArrayBlockingQueue 有界队列底层实现是数组,数组大小是固定的,假如数组一端为头,另一端为尾,那么头和尾构建一个FIFO队列

属性和默认值:

    //存储的数据 存放在数组中
    final Object[] items;
    //读数据位置
    int takeIndex;
    //写入数据位置
    int putIndex;
    //数据数量
    int count;
    //队列同步相关属性
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;

通过 ArrayBlockingQueue 数据结构可知:首先是有一个数组 T[], 用来存储所有的元素,由于 ArrayBlockingQueue 最终设置为一个不可扩展大小的 Queue ,所以这里items就是初始化就固定大小的数组(final),另外有两个索引,头索引 takeIndex ,尾索引 putIndex ,一个队列的大小 count ,要阻塞的话就必须用到一个锁和两个条件(非空,非满),这三个条件都是不可变类型。因为只有一把锁,所以任意时刻对队列只能有一个线程,意味着索引和大小的操作都是线程安全的,所以可以看到takeindex等不需要原子操作和volatile语义了。

构造函数:

 public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
   //通过初始容量和是否公平性抢锁标志来进行实例化
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
    //通过初始容量capacity、公平性标志fair和集合c
    public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {
        this(capacity, fair);
        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            int i = 0;
            try {
                for (E e : c) {
                    //数据是不能为null
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

put操作

可阻塞的添加元素

 public void put(E e) throws InterruptedException {
        //检测插入数据不能为null
        checkNotNull(e);
        //添加可中断的锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) //容量满了需要阻塞
                notFull.await();
            //当前集合未满,执行插入操作
            insert(e);
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    private void insert(E x) {
        items[putIndex] = x;
        putIndex = inc(putIndex);
        ++count;
        //通知take操作已经有数据吗,如果有take方法阻塞,此时可被唤醒来执行take操作
        notEmpty.signal();
    }
  //循环数组的特殊标志处理 ,如果是到最大值则重定向到0号索引
    final int inc(int i) {
        return (++i == items.length) ? 0 : i;
    }

插入操作,在队列满的情况下会阻塞,直到有数据take出队列时才能结束阻塞,将当前数据插入队列。

take方法

将数据从队列中移除

    public E take() throws InterruptedException {
        //添加可中断的锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) //队列中没有数据时,需要阻塞,直到有数据put进入队列通知该操作可以继续执行
                notEmpty.await();
            //有数据时
            return extract();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    private E extract() {
        final Object[] items = this.items;
        E x = this.<E>cast(items[takeIndex]);
        items[takeIndex] = null;
        takeIndex = inc(takeIndex);
        --count;
        //发出通知 通知put方法,唤醒put操作
        notFull.signal();
        return x;
    }

ArrayBlockingQueue特点:

1、底层数据结构是数组,且数组大小一旦确定不可更改

2、不能存储null

3、阻塞功能是通过一个锁和两个隶属于该锁的Condition进行通信完成阻塞

LinkedBlockingQueue:无界队列

LinkedBlockingQueue有两个lock锁和两个Condition以及用于计数的AtomicInteger底层数据结构是链表,都是采用头尾节点,每个节点执行下一个节点的结构数据存储在Node结构中。

引入两把锁,一个入队列锁,一个出队列的锁。满足同时有一个队列不满的Condition和一个队列不空的Condition。

为什么使用两把锁,一把锁是否可以?

一把锁完全可以的,一把锁意味着入队列和出队列同时只能有一个在进行,另一个必须等待释放锁,而从实际实现上来看,head和last是分离的,相互独立的,入队列实现是不会修改出队列的数据的,同理,出队列时也不会修改入队列的数据,这两个操作实际是相互独立,这个锁相当于两个写入锁,入队列是一种写操作,操作head,出队列是一种写操作,操作的是tail,这两是无关的。

SynchronousQueue:同步队列

SynchronousQueue 为同步队列:每个插入操作必须等待另一个线程的移除操作,同样,任何一个移除操作都要等待另一个线程的插入操作,因此此队列中其实没有任何一个数据,或者说容量为0,SynchronousQueue更像一个管道,不像容器,资源从一个方向快速的传递到另一个方向。

队列对比

在这里插入图片描述

  • 如果不需要阻塞队列,优先选择ConcurrentLinkedQueue;
  • 如果需要阻塞队列,队列大小固定优先选择ArrayBlockingQueue;
  • 队列大小不固定优先选择LinkedBlockingQueue;
  • 如果需要对队列进行排序,选择PriorityBlockingQueue;
  • 如果需要一个快速交换的队列,选择SynchronousQueue;
  • 如果需要对队列中的元素进行延时操作,则选择DelayQueue。

到此这篇关于Java阻塞队列中的BlockingQueue接口详解的文章就介绍到这了,更多相关Java阻塞队列BlockingQueue内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于springboot和redis实现单点登录

    基于springboot和redis实现单点登录

    这篇文章主要为大家详细介绍了基于springboot和redis实现单点登录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • RabbitMQ,RocketMQ,Kafka 事务性,消息丢失,消息顺序性和消息重复发送的处理策略问题

    RabbitMQ,RocketMQ,Kafka 事务性,消息丢失,消息顺序性和消息重复发送的处理策略问题

    这篇文章主要介绍了RabbitMQ,RocketMQ,Kafka 事务性,消息丢失,消息顺序性和消息重复发送的处理策略,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • mybatis-generator生成文件覆盖问题的解决

    mybatis-generator生成文件覆盖问题的解决

    这篇文章主要介绍了mybatis-generator生成文件覆盖问题的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • SpringBoot集成iTextPDF的实例

    SpringBoot集成iTextPDF的实例

    SpringBoot集成iTextPDF时,创建PDF文档涉及Document、PdfPTable和PdfPCell对象,设置文档大小和页边距,使用Paragraph设置段落样式,并通过Table和Cell控制表格样式和对齐,还可加入图片美化文档,这些步骤对于生成具有中文内容的PDF文件至关重要
    2024-09-09
  • Java实现限定时间CountDownLatch并行场景

    Java实现限定时间CountDownLatch并行场景

    本文将结合实例代码,介绍Java实现限定时间CountDownLatch并行场景,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • 灵活控制任务执行时间的Cron表达式范例

    灵活控制任务执行时间的Cron表达式范例

    这篇文章主要为大家介绍了灵活控制任务执行时间的Cron表达式范例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • java动态加载插件化编程详解

    java动态加载插件化编程详解

    这篇文章主要介绍了java动态加载插件化编程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • java线程池参数自定义设置详解

    java线程池参数自定义设置详解

    这篇文章主要为大家介绍了java线程池参数自定义设置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • 详解SpringBoot中自定义starter的开发与使用

    详解SpringBoot中自定义starter的开发与使用

    starter是SpringBoot中非常重要的一个机制,他是基于约定优于配置的思想所衍生出来的,本文主要介绍了SpringBoot中自定义starter的开发与使用,感兴趣的可以了解下
    2023-09-09
  • linux系统下查看jdk版本、路径及配置环境变量

    linux系统下查看jdk版本、路径及配置环境变量

    在Linux系统中,配置JDK环境变量是非常重要的,它可以让你在终端中直接使用Java命令,这篇文章主要给大家介绍了关于linux系统下查看jdk版本、路径及配置环境变量的相关资料,需要的朋友可以参考下
    2024-01-01

最新评论