首页 >> 知识 >> Java的7种阻塞队列及其实现原理

Java的7种阻塞队列及其实现原理

队列和阻塞队列队列

队列(Queue)是一种经常使用的集合。Queue 实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。和 List、Set 一样都继承自 Collection。它和 List 的区别在于,List可以在任意位置添加和删除元素,而Queue 只有两个操作:

把元素添加到队列末尾;从队列头部取出元素。

超市的收银台就是一个队列:

蜜桃成人网站入口常用的 LinkedList 就可以当队列使用,实现了 Dequeue 接口,还有 ConcurrentLinkedQueue,他们都属于非阻塞队列。

阻塞队列

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如下:

线程 1 往阻塞队列中添加元素,而线程 2 从阻塞队列中移除元素

当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。当阻塞队列是满时,从队列中添加元素的操作将会被阻塞。

试图从空的阻塞队列中获取元素的线程将会阻塞,直到其他的线程往空的队列插入新的元素,同样,试图往已满的阻塞队列添加新元素的线程同样也会阻塞,直到其他的线程从列中移除一个或多个元素或者完全清空队列后继续新增。

类似蜜桃成人网站入口去海底捞排队,海底捞爆满情况下,阻塞队列相当于用餐区,用餐区满了的话,就阻塞在候客区等着,可以用餐的话 put 一波去用餐,吃完就 take 出去。

为什么要用阻塞队列

在多线程领域:所谓阻塞,是指在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒。

那为什么需要 BlockingQueue 呢

好处是蜜桃成人网站入口不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这些 BlockingQueue 都包办了。

在 concurrent 包发布以前,多线程环境下,蜜桃成人网站入口每个程序员都必须自己去实现这些细节,尤其还要兼顾效率和线程安全,这会给蜜桃成人网站入口的程序带来不小的复杂性。现在有了阻塞队列,蜜桃成人网站入口的操作就从手动挡换成了自动挡。

Java里的阻塞队列

Collection的子类除了蜜桃成人网站入口熟悉的 List 和 Set,还有一个 Queue,阻塞队列 BlockingQueue 继承自 Queue。

BlockingQueue 是个接口,需要使用它的实现之一来使用 BlockingQueue,java.util.concurrent 包下具有以下 BlockingQueue 接口的实现类:

JDK 提供了 7 个阻塞队列。分别是:

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列DelayQueue:一个使用优先级队列实现的无界阻塞队列SynchronousQueue:一个不存储元素的阻塞队列LinkedTransferQueue:一个由链表结构组成的无界阻塞队列(实现了继承于 BlockingQueue 的 TransferQueue)LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列BlockingQueue核心方法

相比 Queue 接口,BlockingQueue 有四种形式的 API。

方法类型抛出异常返回特殊值一直阻塞超时退出插入add(e)offer(e)put(e)offer(e,time,unit)移除(取出)remove()poll()take()poll(time,unit)检查element()peek()不可用不可用

以 ArrayBlockingQueue 为例来看下 Java 阻塞队列提供的常用方法

抛出异常:当阻塞队列满时,再往队列里 add 插入元素会抛出 java.lang.IllegalStateException: Queue full 异常;当队列为空时,从队列里 remove 移除元素时会抛出 NoSuchElementException 异常 。element(),返回队列头部的元素,如果队列为空,则抛出一个 NoSuchElementException 异常

返回特殊值:offer(),插入方法,成功返回 true,失败返回 false;poll(),移除方法,成功返回出队列的元素,队列里没有则返回 nullpeek() ,返回队列头部的元素,如果队列为空,则返回 null

一直阻塞:当阻塞队列满时,如果生产线程继续往队列里 put 元素,队列会一直阻塞生产线程,直到拿到数据,或者响应中断退出;当阻塞队列空时,消费线程试图从队列里 take 元素,队列也会一直阻塞消费线程,直到队列可用。

超时退出:当阻塞队列满时,队列会阻塞生产线程一定时间,如果超过一定的时间,生产线程就会退出,返回 false当阻塞队列空时,队列会阻塞消费线程一定时间,如果超过一定的时间,消费线程会退出,返回 null

BlockingQueue 实现类

逐个分析下这 7 个阻塞队列,常用的几个顺便探究下源码。

ArrayBlockingQueue

ArrayBlockingQueue,一个由数组实现的有界阻塞队列。该队列采用先进先出(FIFO)的原则对元素进行排序添加的。

ArrayBlockingQueue 为有界且固定,其大小在构造时由构造函数来决定,确认之后就不能再改变了。

ArrayBlockingQueue 支持对等待的生产者线程和使用者线程进行排序的可选公平策略,但是在默认情况下不保证线程公平的访问,在构造时可以选择公平策略(fair = true)。公平性通常会降低吞吐量,但是减少了可变性和避免了“不平衡性”。(ArrayBlockingQueue 内部的阻塞队列是通过 ReentrantLock 和 Condition 条件队列实现的, 所以 ArrayBlockingQueue 中的元素存在公平和非公平访问的区别)

所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素,可以保证先进先出,避免饥饿现象。

源码解读

public class ArrayBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable { // 通过数组来实现的队列 final Object[] items; //记录队首元素的下标 int takeIndex; //记录队尾元素的下标 int putIndex; //队列中的元素个数 int count; //通过ReentrantLock来实现同步 final ReentrantLock lock; //有2个条件对象,分别表示队列不为空和队列不满的情况 private final Condition notEmpty; private final Condition notFull; //迭代器 transient Itrs itrs; //offer方法用于向队列中添加数据 public boolean offer(E e) { // 可以看出添加的数据不支持null值 checkNotNull(e); final ReentrantLock lock = this.lock; //通过重入锁来实现同步 lock.lock(); try { //如果队列已经满了的话直接就返回false,不会阻塞调用这个offer方法的线程 if (count == items.length) return false; else { //如果队列没有满,就调用enqueue方法将元素添加到队列中 enqueue(e); return true; } } finally { lock.unlock(); } } //多了个等待时间的 offer方法 public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { checkNotNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; //获取可中断锁 lock.lockInterruptibly(); try { while (count == items.length) { if (nanos
网站地图