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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java开发微信分享接口的步骤

    java开发微信分享接口的步骤

    这篇文章主要为大家详细介绍了java开发微信分享接口的步骤,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 使用maven编译Java项目实例

    使用maven编译Java项目实例

    这篇文章主要介绍了使用maven编译Java项目实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06
  • Java判断时间间隔是否超限的两种实现方法详解

    Java判断时间间隔是否超限的两种实现方法详解

    本文探讨了业务系统中判断时间间隔是否超限的两种实现方法,主要针对常见的30分钟支付、72小时检测等需求,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下
    2026-01-01
  • 微信小程序中的openid的作用详解

    微信小程序中的openid的作用详解

    微信小程序作为连接用户与服务的重要桥梁,在提升用户体验方面发挥着重要作用,本文章将详细探讨openid在微信小程序中的作用及其重要性,帮助开发者更好地理解和利用这一功能,优化应用体验并增强安全性,感兴趣的朋友一起看看吧
    2025-04-04
  • 一文详解如何在Java中自定义异常类

    一文详解如何在Java中自定义异常类

    这篇文章主要介绍了如何在Java中自定义异常类的相关资料,在Java编程中开发者可以通过继承Exception类或其子类创建自定义异常,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-10-10
  • SpringBoot启动后运行代码的几种方法

    SpringBoot启动后运行代码的几种方法

    在开发SpringBoot应用时,有时需要在应用启动后执行一些特定的代码,例如监控目录变化、初始化数据等,然而,直接在启动时运行代码可能会遇到@Autowired服务未初始化的问题,因此需要找到合适的时机来执行这些代码,所以本文给大家介绍了SpringBoot启动后运行代码的几种方法
    2025-06-06
  • Java正则替换手机号代码实例

    Java正则替换手机号代码实例

    本文的主要内容是Java语言中正则表达式替换手机号的第4到第7位,实现方法十分简单,同时涉及了一些正则表达式的相关用法,需要的朋友可以参考下。
    2017-09-09
  • springboot+WebMagic+MyBatis爬虫框架的使用

    springboot+WebMagic+MyBatis爬虫框架的使用

    本文是对spring boot+WebMagic+MyBatis做了整合,使用WebMagic爬取数据,然后通过MyBatis持久化爬取的数据到mysql数据库。具有一定的参考价值,感兴趣的可以了解一下
    2021-08-08
  • Spring Boot热加载jar实现动态插件的思路

    Spring Boot热加载jar实现动态插件的思路

    本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件
    2021-10-10
  • Java实现获取前、后N天日期的函数分享

    Java实现获取前、后N天日期的函数分享

    本文给大家分享的是使用java实现的获取当前日期前后N天的函数,非常的简单实用,有需要的小伙伴可以参考下。
    2015-03-03

最新评论