2025年阻塞队列的原理(阻塞队列offer方法)

阻塞队列的原理(阻塞队列offer方法)div id navCategory div p style text align left 大家好 我是小黑 一个在互联网苟且偷生的农民工 p 学过数据结构的同学应该都知道 队列是数据结构中一种特殊的线性表结构 和平时使用的 List Set 这些数据结构相比有点特殊

大家好,我是讯享网,很高兴认识大家。



 <div id="navCategory"></div><p style="text-align: left">大家好,我是小黑,一个在互联网苟且偷生的农民工。</p> 

讯享网

学过数据结构的同学应该都知道,队列是数据结构中一种特殊的线性表结构,和平时使用的List,Set这些数据结构相比有点特殊,它的特殊之处在于它只允许在队列的头部(Head)进行删除操作,在尾部(Tail)进行插入操作,这种方式的队列我们称之为先进先出队列(FIFO)。


讯享网

在JDK1.5中推出了队列这一数据结构的具体实现,接口Queue是对于队列的定义,并有一些列具有特殊功能的队列实现。

在Queue接口中定义了队列的如下方法:

其中add(E)并非Queue接口新定义,而是从Collection接口继承而来的。

BlockingQueue接口也是在JDK1.5中推出,存放在java.util.concurrent包中,继承自Queue,所以在BlockingQueue中有Queue的所有方法。

从名字就可以看出BlockingQueue是一种阻塞队列,它支持在检索元素时如果队列为空可以一直阻塞等待直到有元素可以获取,同样在添加元素时如果队列已满会阻塞等待队列中有空闲的存储空间。

BlockingQueue的方法可以归纳为四类:

  • 在操作时如不能立即满足,会直接抛出异常
  • 在操作时如不能立即满足,则返回特殊的值,如插入、移除方法会返回false,检查方法会返回null
  • 在操作时如不能立即满足,则会阻塞等待,直到操作成功
  • 在操作时如不能立即满足,则会阻塞等待给定的时间长度,时间到达后如果还不能满足则返回null

这四类方法总结如下。

因为在BlockingQueue的一些方法中,会通过null表示某种操作的失败,所以不允许在BlockingQueue中存放null值元素,会在操作时抛出NullPointerExection异常。

BlockingQueue因为是一个容器嘛,所以它也有容量的限制,在具体实现类中有可以设置容量的实现类,也有不可以设置容量的实现类,不能设置容量的实现类容量默认为Integer.MAX_VALUE。

BlockingQueue是定义在java.util.concurrent包中,那么它在并发情况下到底是不是线程安全的呢?

在JDK提供的BlockingQueue的具体实现类中,上面表格中的方法实现都是线程安全的,在内部都使用了锁或者其他形式的并发控制保证操作的原子性。

但是有一点要注意,就是一些批量处理的方法例如addAll、containsAll、retainAll和removeAll这些方法并不一定是线程安全的,使用时注意。

说完BlockingQueue接口我们接下来看看它都有哪些具体的实现呢?以及在它们内部是如何做到线程安全和阻塞的呢?

ArrayBlockingQueue是一个底层由数组支持额有界阻塞队列。

先来看看ArrayBlockingQueue中都有哪些属性。

我们通过这些队列中的属性基本可以知道ArrayBlockingQueue中都有哪些重要信息,可以看出ArrayBlockingQueue就是使用Object[]来存放元素的。

那么应该如何创建一个ArrayBlockingQueue呢?

默认的构造方法需要传入一个int类型的capacity表示该队列的容量。在该构造方法中会调用另一个构造方法,传入一个默认值false。

从这个方法我们看出传入的false表示会在内部用于创建一个ReentrantLock对象,我们都知道ReentrantLock支持公平和非公平的实现,我们猜想一下,这里的这个fair值是不是表示该阻塞队列对于阻塞排队的线程支持公平和非公平的策略呢?这里先卖个关子,在后面的方法中我们具体说。

除了这两种创建的方式,ArrayBlockingQueue还支持传入一个Collection集合。

先来看看添加一个新元素到ArrayBlockingQueue是如何实现的,怎样保证线程安全的。

add(e)

add方法的实现逻辑本质上是对offer方法套了一层壳,如果offer方法返回false时,抛出异常。所以我们直接看offer方法的实现就好。

offer(e)

可以看到offer方法的逻辑还是比较简单的,先检查入参不能为空,然后加锁保证入队操作的原子性,在获取锁成功后入队,如果队列已满则直接返回false,所以offer方法并不会阻塞。

put(e)

put方法和offer方法唯一的区别,就是会在队列满的时候使用Condition条件对象notFull阻塞等待。

在enqueue方法中才会完成对队列中的数组元素的赋值动作,完成之后唤醒阻塞等待的移除元素操作线程。

offer(e,time,unit)

offer(e,time,unit)方法与offer(e)方法相比,主要时多了一个等待时间,会在时间到达时如果没有空间添加元素返回false。

ArrayBlockingQueue中移除元素的方法主要有remove(),poll(),take(),poll(time,unit)四个。这几个方法的实现逻辑都比较简单,这里不在单独贴代码 。我们来看一下阻塞方法take()的实现即可。

take()

dequeue()

LinkedBlockingQueue是一个基于链表结构的阻塞队列,可以在创建时指定边界大小,也可以不指定,在不指定边界时容量为Integer.MAX_VALUE。

我们先来看看在LinkedBlockingQueue中都有哪些重要的属性。

在LinkedBlockingQueue中使用Node来存放元素,和指向下一个节点的链表指针。

在LinkedBlockingQueue的构造方法中,会创建一个创建一个不存放元素的Node对象赋值给head和last。

offer(e)

对于链表结构的LinkedBlockingQueue来说,入队操作要简单很多,只需要将node节点挂在最后一个节点last的next,然后将自己赋值给last。

put(e)

对比结果也和我们最开始的方法汇总表格一样,offer(e)方法会在入队时如果队列已满直接返回false,而put(e)会一直阻塞等待,知道入队成功。

add(e)方法和offer(e,time,unit)方法实现逻辑上没有特殊之处,这里不再放源码。

poll()

poll()方法会在元素出队时如果没有元素则直接返回null。

take()

同样,take方法会在没有元素时一直等待。

我们来对比一下ArrayBlockingQueue和LinkedBlockingQueue都有哪些区别。

  • ArrayBlockingQueue基于数组实现,LinkedBlockingQueue基于链表实现
  • ArrayBlockingQueue在添加和移除元素的操作中共用一把锁,LinkedBlockingQueue使用takeLock和putLock两把锁
  • ArrayBlockingQueue在添加和移除元素时直接使用元素的类型处理,LinkedBlockingQueue需要转成Node对象
  • ArrayBlockingQueue创建时必须指定容量,LinkedBlockingQueue可以不指定,默认容量为Integer.MAX_VALUE

由于LinkedBlockingQueue使用两把锁将入队操作和出队操作分离,这会大大提高队列的吞吐量,在高并发情况下生产者和消费者可以并行处理,提高并发性能。

但是LinkedBlockingQueue默认是无界队列,要小心内存溢出风险,所以最好在创建时指定容量大小。

BlockingQueue接口的实现类除了本期介绍的这两种,还有PriorityBlockingQueue,SynchronousQueue,LinkedBlockingDeque等,每一个都有它独特的特性和使用场景,后面我们再单独深入解析。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

小讯
上一篇 2025-04-19 15:22
下一篇 2025-06-12 07:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/200431.html