一口气说出18种队列(Queue),面试稳了!

在讲《21张图讲解集合的线程不安全》那一篇,我留了一个彩蛋,就是Queue(队列)尚未讲,此次咱们重点来看看Java中的Queue家族,总共涉及到18种Queue。这篇恐怕是市面上最全最细 讲解Queue的。java

本篇主要内容以下:程序员

本篇主要内容

帮你总结好的阻塞队列:面试

18种Queue总结

1、Queue自我介绍

队列原理图

1.1 Queue自我介绍

hi,你们好,个人英文名叫Queue,中文名叫队列,不管现实生活中仍是计算机的世界中,我都是一个很重要的角色哦~算法

我是一种数据结构,你们能够把我想象成一个数组,元素从个人一头进入、从另一头出去,称为FIFO原则(先进先出原则)。编程

我还有两个亲兄弟:List(列表)、Set(集),他们都是Collection的儿子,我还有一个远房亲戚:Map(映射)。他们都是java.util包这个你们庭的成员哦~小程序

1.2 现实生活中的场景

  • 海底捞排号等位(先排号的优先进餐厅)
  • 邮政员寄送信件(信箱是队列)

1.3 计算机世界中的场景

  • 消息队列 RabbitMQ
  • UDP协议(接收端将消息存放在队列中,从队列中读取数据)

2、高屋建瓴,纵览全局

18种队列分为三大类: 接口、抽象类、普通类。数组

弄清楚下面的继承实现关系对后面理解18种队列有很大帮助。缓存

18个Queue的继承实现关系图

  • Queue接口继承 Collection接口,Collection接口继承 Iterable接口
  • BlockingQueue接口、Deque接口 继承 Queue接口
  • AbstractQueue抽象类实现 Queue接口
  • BlockingDeque接口、TransferQueue接口继承 BlockingQueue接口
  • BlockingDeque接口继承Deque接口
  • LinkedBlockingDeque实现 BlockingDeque接口
  • LinkedTransferQueue类接口实现 TransferQueue接口
  • LinkedList类、ArrayDeque类、ConcurrentLinkedDeque实现Deque接口
  • ArrayBlockingQueue类、LinkendBlockingQueue类、LinkedBlockingDeque类、LinkedTransferQueue类、SynchronousQueue类、PriorityBlockQueue类、DelayQueue类继承AbstractQueue抽象类和实现了BlockingQueue接口
  • PriorityQueue类和ConcurrentLinkedQueue继承AbstractQueue抽象类

注意:安全

  • Deque:全称Double-Ended queue,表示双端队列。
  • 类实现接口,用implements
  • 接口继承接口,用 extends
  • 类继承类,用extends

3、万物归宗Queue接口

2.1 深刻理解Queue接口的本质

  • Queue接口是一种Collection,被设计用于处理以前临时保存在某处的元素。markdown

  • 除了基本的Collection操做以外,队列还提供了额外的插入、提取和检查操做。每一种操做都有两种形式:若是操做失败,则抛出一个异常;若是操做失败,则返回一个特殊值(null或false,取决因而什么操做)。

  • 队列一般是以FIFO(先进先出)的方式排序元素,可是这不是必须的。

  • 只有优先级队列能够根据提供的比较器对元素进行排序或者是采用正常的排序。不管怎么排序,队列的头将经过调用remove()或poll()方法进行移除。在FIFO队列种,全部新的元素被插入到队尾。其余种类的队列可能使用不一样的布局来存放元素。

  • 每一个Queue必须指定排序属性。

2.2 Queue接口的核心方法

总共有3组方法,每一组方法对应两个方法。以下图所示:

Queue的核心方法

动做 抛异常 返回特殊值
Insert add(e) offer(e)
Remove remove() poll
Examine element() peek()
  • (1)好比添加(Insert)元素的动做,会有两种方式:add(e)offer(e)。若是调用add(e)方法时,添加失败,则会抛异常,而若是调用的是offer(e)方法失败时,则会返回false。offer方法用于异常是正常的状况下使用,好比在有界队列中,优先使用offer方法。假如队列满了,不能添加元素,offer方法返回false,这样咱们就知道是队列满了,而不是去handle运行时抛出的异常。

  • (2)同理,移除(Remove)元素的动做,队列为空时,remove方法抛异常,而poll返回null。若是移除头部的元素成功,则返回移除的元素。

  • (3)同理,检测(Examine)元素的动做,返回头部元素(最开始加入的元素),但不删除元素, 若是队列为空,则element()方法抛异常,而peek()返回false。

  • (4)Queue接口没有定义阻塞队列的方法,这些方法在BlockQueue接口中定义了。

  • (5)Queue实现类一般不容许插入null元素,尽管一些实现类好比LinkedList不由止插入null,可是仍是不建议插入null,由于null也被用在poll方法的特殊返回值,以说明队列不包含元素。

4、双端可用Deque接口

4.1 深刻理解Deque接口的原理

双端队列Deque

(1)Deque概念: 支持两端元素插入和移除的线性集合。名称deque是双端队列的缩写,一般发音为deck。大多数实现Deque的类,对它们包含的元素的数量没有固定的限制的,支持有界和无界。

(2)Deque方法说明:

Deque方法

**说明: **

  • 该列表包含包含访问deque两端元素的方法,提供了插入,移除和检查元素的方法。

  • 这些方法种的每一种都存在两种形式:若是操做失败,则会抛出异常,另外一种方法返回一个特殊值(null或false,取决于具体操做)。

  • 插入操做的后一种形式专门设计用于容量限制的Deque实现,大多数实现中,插入操做不能失败,因此能够用插入操做的后一种形式。

  • Deque接口扩展了Queue接口,当使用deque做为队列时,做为FIFO。元素将添加到deque的末尾,并从头开始删除。

  • 做为FIFO时等价于Queue的方法以下表所示:

Deque等价于Queue的方法

好比Queue的add方法和Deque的addLast方法等价。

  • Deque也能够用做LIFO(后进先出)栈,这个接口优于传统的Stack类。看成为栈使用时,元素被push到deque队列的头,而pop也是从队列的头pop出来。

  • Stack(栈)的方法正好等同于Deque的以下方法:

    Dque等价于Stack的方法

注意:peek方法不管是做为栈仍是队列,都是从队列的检测队列的头,返回最早加入的元素。好比第一次put 100,第二次put 200,则peek返回的是100。以下图所示:

示例代码

4.1 哪些类实现了Deque接口

  • LinkedList类
  • ArrayDeque类
  • ConcurrentLinkedDeque类
  • LinkedBlockingDeque类

4.2 哪些类继承了Deque接口

  • BlockingDeque接口

5、队列骨架AbstractQueue抽象类

5.1 深刻理解AbstractQueue抽象类

AbstractQueue是一个抽象类,继承了Queue接口,提供了一些Queue操做的骨架实现。

AbstractQueue的方法

方法add、remove、element方法基于offer、poll和peek。也就是说若是不能正常操做,则抛出异常。咱们来看下AbstactQueue是怎么作到的。

  • AbstractQueue的add方法
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}
复制代码
  • AbstractQueue的remove方法
public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
复制代码
  • AbstractQueue的element方法
public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
复制代码

注意:

  • 若是继承AbstractQueue抽象类则必须保证offer方法不容许null值插入。

5.2 哪些类继承了AbstractQueue抽象类

  • ArrayBlockingQueue类、LinkendBlockingQueue类、LinkedBlockingDeque类、LinkedTransferQueue类、SynchronousQueue类、PriorityBlockQueue类、DelayQueue类继承AbstractQueue抽象类
  • PriorityQueue类和ConcurrentLinkedQueue继承AbstractQueue抽象类

6、阻塞缓冲BlockingQueue接口

6.1 宏观来看BlockingQueue(阻塞队列)

  • BlockQueue满了,PUT操做被阻塞

阻塞队列满了的状况

  • BlockQueue为空,Take操做被阻塞

阻塞队列为空的状况

(1)BlockingQueue(阻塞队列)也是一种队列,支持阻塞的插入和移除方法。

(3)阻塞的插入:当队列满时,队列会阻塞插入元素的线程,直到队列不满。

(4)阻塞的移除:当队列为空,获取元素的线程会等待队列变为非空。

(5)应用场景:生产者和消费者,生产者线程向队列里添加元素,消费者线程从队列里移除元素,阻塞队列时获取和存放元素的容器。

(6)为何要用阻塞队列:生产者生产和消费者消费的速率不同,须要用队列来解决速率差问题,当队列满了或空的时候,则须要阻塞生产或消费动做来解决队列满或空的问题。

6.2 案例解析

线程A往阻塞队列(Blocking Queue)中添加元素,而线程B从阻塞队列中移除元素。

  • 当阻塞队列为空的时候 (一个元素都没有),则从队列中获取元素的操做将会被阻塞。
    • 生活中的案例:去海底捞吃火锅的时候,早上8点没人来吃火锅,因此须要等客人过来。
    • 翻译成线程:如今没有元素须要添加,并且阻塞队列为空,因此线程B不须要从队列中拿元素出来,因此线程B获取元素的操做被阻塞了。
  • 当阻塞队列满了的时候 (全部位置都放有元素),则从队列中添加元素的操做将会被阻塞。
    • 生活中的案例:去海底捞吃火锅的时候,人太多了,须要排号,等其余桌空出来了才能进去。
    • 翻译成线程:线程A往阻塞队列中添加元素,将队列填满了,线程B如今正在忙,没法拿出队列中的元素,因此阻塞队列没有地方再放元素了,这个时候线程A添加元素的操做就被阻塞了

6.3 操刀BlockingQueue接口

BlockingQueue接口的10个核心方法:

继承的方法

10个核心方法总结以下:

BlockingQueue接口的10个核心方法

有三大类操做:插入、移除、检查。

  • 插入有四种方法: add、offer、put、offer超时版。
    • add方法特别之处用于添加失败时抛出异常,共有四种异常:
      • IllegalStateException - 队列满了
      • ClassCastException - 添加的元素类型不匹配
      • NullPointerException - 添加的元素为null
      • IllegalArgumentException - 添加的元素某些属性不匹配
    • offer方法特别之处用于添加失败时只返回false
    • put方法特别之处用于当阻塞队列满时,生产者若是往队列里put元素,则队列会一直阻塞生产者线程,直到队列可用或者响应中断退出
    • offer超时方法特别之处用于当阻塞队列满时,生产者若是往队列里面插入元素,队列会阻塞生产者线程一段时间,若是超过了指定时间,生产者线程会退出,并返回false。
  • 移除有四种方法: remove、poll、take、poll超时版
    • remove方法特别之处用于移除失败时抛出异常
      • NoSuchElementException - 若是这个队列是空的
    • poll方法特别之处用于移除失败时返回null
    • take方法特别之处用于当阻塞队列为空时,消费者线程若是从队列里面移除元素,则队列会一直阻塞消费者线程,直到队列不为空
    • poll超时方法特别之处用于当阻塞队列空时,消费者若是从队列里面删除元素,则队列会一直阻塞消费者线程,若是超过了指定时间,消费者线程会退出,并返回null
  • 检查有两种方法: element、peek
    • element方法用于检测头部元素的存在性,若是队列为空,则抛出异常,不然返回头部元素。
    • peek方法用于检测头部元素的存在性,若是队列为空,返回特殊值null,不然返回头部的元素。

6.4 BlockingQueue经过什么来阻塞插入和移除的?

  • 当往队列里插入一个元素时,若是队列不可用,那么阻塞生产者主要经过LockSupport. park(this)来实现。
  • park这个方法会阻塞当前线程,只有如下4种状况中的一种发生时,该方法才会返回。
    • 与park对应的unpark执行或已经执行时。“已经执行”是指unpark先执行,而后再执行park的状况。
    • 线程被中断时。
    • 等待完time参数指定的毫秒数时。
    • 异常现象发生时,这个异常现象没有任何缘由。

6.5 哪些类继承了BlockingQueue接口?

  • BlockingDeque接口 - 双端阻塞队列
  • TransferQueue接口 - 传输队列

6.6 哪些类实现了BlockingQueue接口?

  • ArrayBlockingQueue类 - 由数组构成的有界阻塞队列
  • LinkedBlockingQueue类 - 由链表构成的有界阻塞队列,界限默认大小为Integer.MAX_Value(2^31-1),值很是大,至关于无界。
  • LinkedBlockingDeque类 - 由链表构成的双向阻塞队列
  • LinkedTransferQueue类 - 由链表构成的无界阻塞队列
  • SynchronousQueue类 - 不存储元素的阻塞队列,只有一个元素进行数据传递。
  • LinkedTransferQueue类 - 由链表构成的无界阻塞TransferQueue队列
  • DelayQueue类 - 使用优先级队列实现的延迟无界阻塞队列

6.6 BlockingQueue接口继承了哪些接口

  • BlockingQueue接口继承了Queue接口,可做为队列使用

7、双端阻塞BlockingDeque接口

7.1 从原理图上理解BlockDeque

  • BlockQueue满了,两端的Take操做被阻塞

BlockingDeque满了

  • BlockQueue为空,两端的Take操做被阻塞

BlockQueue为空

7.2 BlockingDeque接口方法

是阻塞队列BlockingQueue和双向队列Deque接口的结合。有以下方法:

BlockingDeque接口方法

示例:

尝试执行如下方法:

LinkedBlockingDeque queue = new LinkedBlockingDeque();
queue.addFirst("test1");
queue.addFirst(300);
queue.addLast("400");
复制代码

最后队列中的元素顺序以下:

300, test1, 400。

先添加了test1放到队列的头部,而后在头部的前面放入300,因此300在最前面,成为头部,而后将400放入队列的尾部,因此最后的结果是300, test1, 400。

队列种的元素

7.3 BlockDeque和BlockQueue的对等方法

mark

7.4 BlockingDeque的特色

  • 线程安全。
  • 不容许使用null元素。
  • 无界和有界均可以。

7.5 BlockingDeque接口继承了哪些接口?

  • Queue接口,具备队列的功能
  • Deque接口,具备双端队列的功能
  • BlockingQueue接口,可做为阻塞队列使用

7.6 哪些类实现了BlockDeque接口?

  • LinkedBlockingDeque

8、使命必达TransferQueue接口

8.1 Transfer怎么理解?

若是有消费者正在获取元素,则将队列中的元素传递给消费者。若是没有消费者,则等待消费者消费。我把它称做使命必达队列,必须将任务完成才能返回。

8.2 生活中的案例

  • **针对TransferQueue的transfer方法 **
    • 圆通快递员要将小明的2个快递送货到门,韵达快递员也想将小明的2个快递送货到门。小明一次只能拿一个,快递员必须等小明拿了一个后,才能继续给第二个。
  • 针对TransferQueue的tryTransfer方法
    • 圆通快递员要将小明的2个快递送货到门,韵达快递员也想将小明的2个快递送货到门。发现小明不在家,就把快递直接放到菜鸟驿站了。
  • 针对TransferQueue的tryTransfer超时方法
    • 圆通快递员要将小明的2个快递送货到门,韵达快递员也想将小明的2个快递送货到门。发现小明不在家,因而先等了5分钟,发现小明尚未回来,就把快递直接放到菜鸟驿站了。

8.3 TransferQueue的原理解析

  • transfer(E e)

    原理以下图所示:

    transfer方法的原理

    • 原理图解释:生产者线程Producer Thread尝试将元素B传给消费者线程,若是没有消费者线程,则将元素B放到尾节点。而且生产者线程等待元素B被消费。当元素B被消费后,生产者线程返回。
    • 若是当前有消费者正在等待接收元素(消费者经过take方法或超时限制的poll方法时),transfer方法能够把生产者传入的元素马上transfer(传输)给消费者。
    • 若是没有消费者等待接收元素,transfer方法会将元素放在队列的tail(尾)节点,并等到该元素被消费者消费了才返回。
  • tryTransfer(E e)

    • 试探生产者传入的元素是否能直接传给消费者。
    • 若是没有消费者等待接收元素,则返回false。
    • 和transfer方法的区别是,不管消费者是否接收,方法当即返回。
  • tryTransfer(E e, long timeout, TimeUnit unit)

    • 带有时间限制的tryTransfer方法。
    • 试图把生产者传入的元素直接传给消费者。
    • 若是没有消费者消费该元素则等待指定的时间再返回。
    • 若是超时了尚未消费元素,则返回false。
    • 若是在超时时间内消费了元素,则返回true。
  • getWaitingConsumerCount()

    • 获取经过BlockingQueue.take()方法或超时限制poll方法等待接受元素的消费者数量。近似值。
    • 返回等待接收元素的消费者数量。
  • hasWaitingConsumer()

    • 获取是否有经过BlockingQueue.tabke()方法或超时限制poll方法等待接受元素的消费者。
    • 返回true则表示至少有一个等待消费者。

8.3 TransferQueue接口继承了哪些接口?

  • BlockingQueue接口,可做为阻塞队列使用
  • Queue接口,可做为队列使用

8.4 哪些类实现了TransferQueue接口?

  • LinkedTranferQueue接口

9、优先由你PriorityQueue类

9.1 理解PriorityQueue类

  • 本应该按照升序排序

本应该按照升序排序

  • 按照倒叙排序

按照自定义优先级排序

  • PriorityQueue是一个支持优先级的无界阻塞队列。

  • 默认天然顺序升序排序。

  • 能够经过构造参数Comparator来对元素进行排序。

public PriorityQueue(Comparator<? super E> comparator) {
     this(DEFAULT_INITIAL_CAPACITY, comparator);
}
复制代码
  • 自定义实现comapreTo()方法来指定元素排序规则。
public Comparator<? super E> comparator() {
    return comparator;
}
复制代码
  • 不容许插入null元素。
  • 实现PriorityQueue接口的类,不保证线程安全,除非是PriorityBlockingQueue。
  • PriorityQueue的迭代器不能保证以任何特定顺序遍历元素,若是须要有序遍历,请考虑使用Arrays.sort(pq.toArray)
  • 进列(offeradd)和出列( pollremove())的时间复杂度O(log(n))。
  • remove(Object) 和 contains(Object)的算法时间复杂度O(n)。
  • peek、element、size的算法时间复杂度为O(1)。

9.2 PriorityQueue类继承了哪些类?

  • AbstractQueue抽象类,具备队列的功能

9.2 PriorityQueue类实现了哪些接口?

  • Queue接口,可做为队列使用。

10、双向链表LinkedList类

10.1 LinkedList的结构

  • LinkedList实现了List和Deque接口,因此是一种双链表结构,能够看成堆栈、队列、双向队列使用。
  • 一个双向列表的每个元素都有三个整数值:元素、向后的节点连接、向前的节点连接

LinkedList的结构

咱们来看下节点类Node

private static class Node<E> {
    E item; //元素
    Node<E> next; //向后的节点连接
    Node<E> prev; //向前的节点连接

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
复制代码

10.2 与ArrayList的区别

  • 1.LinkedList的增长和删除效率相对较高,而查找和修改的效率相对较低。

  • 2.如下状况建议使用ArrayList

    • 频繁访问列表中的一个元素。
    • 只在列表的首尾添加元素。
  • 3.如下状况建议使用LinkedList

    • 频繁地在列表开头、中间、末尾添加和删除元素。
    • 须要经过循环迭代来访问列表中的元素。

10.3 LinkedList不是线程安全的

LinkedList不是线程安全的,因此可使用以下方式保证线程安全。

List list = Collections.synchronizedList(new LinkedList<>());
复制代码

10.4 LinkedList的家庭成员关系

  • LinkedList 继承了 AbstractSequentialList 类。

  • LinkedList 实现了 Queue 接口,可做为队列使用。

  • LinkedList 继承了 AbstractQueue抽象类,具备队列的功能。

  • LinkedList 实现了 List 接口,可进行列表的相关操做。

  • LinkedList 实现了 Deque 接口,可做为双向队列使用。

  • LinkedList 实现了 Cloneable 接口,可实现克隆。

  • LinkedList 实现了 java.io.Serializable 接口,便可支持序列化,能经过序列化去传输。

11、并发安全ConcurrentLinkedQueue类

11.1 理解ConcurrentLinkedQueue

ConcurrentLinkedQueue原理

  • ConcurrentLinked是由链表结构组成的线程安全的先进先出无界队列。
  • 当多线程要共享访问集合时,ConcurrentLinkedQueue是一个比较好的选择。
  • 不容许插入null元素
  • 支持非阻塞地访问并发安全的队列,不会抛出ConcurrentModifiationException异常。
  • size方法不是准确的,由于在统计集合的时候,队列可能正在添加元素,致使统计不许。
  • 批量操做addAll、removeAll、retainAll、containsAll、equals和toArray不保证原子性(操做不可分割)
  • 添加元素happen-before其余线程移除元素。
  • 用法以下:
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
BuildingBlockWithName buildingBlock = new BuildingBlockWithName("三角形", "A");
concurrentLinkedQueue.add(buildingBlock);
复制代码

11.2 ConcurrentLinkedQueue类继承了哪些类?

  • AbstractQueue抽象类,具备队列的功能

11.3 ConcurrentLinkedQueue类实现了哪些接口?

  • Queue接口,可做为队列使用

12、双向数组ArrayDeque类

ArrayDeque原理图

12.1 理解ArrayDeque

  • 由数组组成的双端队列。
  • 没有容量限制,根据须要扩容。
  • 不是线程安全的。
  • 禁止插入null元素。
  • 当用做栈时,比栈速度快,当用做队列时,速度比LinkList快。
  • 大部分方法的算法时间复杂度为O(1)。
  • remove、removeFirstOccurrence、removeLastOccurrence、contains、remove 和批量操做的算法时间复杂度O(n)

12.2 使用方法

建立一个ArrayDeque,往arrayDeque队尾添加元素。

ArrayDeque arrayDeque = new ArrayDeque();
for (int i = 0; i < 50; i++) {
    arrayDeque.add(buildingBlock); // add方法等价于addLast方法
}
复制代码

12.3 ArrayDeque实现了哪些接口

  • Deque接口 - 可用于双端队列

十3、双向并发ConcurrentLinkedDeque类

13.1 理解ConcurrentLinkedDeque类

ConcurrentLinkedDeque原理图

  • 由链表结构组成的双向无界阻塞队列
  • 插入、删除和访问操做能够并发进行,线程安全的类
  • 不容许插入null元素
  • 在并发场景下,计算队列的大小是不许确的,由于计算时,可能有元素加入队列。
  • 批量操做addAll、removeAll、retainAll、containsAll、equals和toArray不保证原子性(操做不可分割)

13.2 ConcurrentLinkedDeque使用示例

建立两个积木:三角形、四边形,而后添加到队列:

BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四边形", "B");
ConcurrentLinkedDeque concurrentLinkedDeque = new ConcurrentLinkedDeque();
concurrentLinkedDeque.addFirst(buildingBlock1);
concurrentLinkedDeque.addLast(buildingBlock2);
//结果:顺序:三角形、四边形
复制代码

13.3 ConcurrentLinkedDeque实现了哪些接口

  • Deque接口 - 可用于双端队列

十4、数组阻塞ArrayBlockingQueue类

14.1 理解ArrayBlockingQueue

ArrayBlockingQueuey原理图

  • ArrayBlockingQueue是一个用数组实现的有界阻塞队列。
  • 队列慢时插入操做被阻塞,队列空时,移除操做被阻塞。
  • 按照先进先出(FIFO)原则对元素进行排序。
  • 默认不保证线程公平的访问队列。
  • 公平访问队列:按照阻塞的前后顺序访问队列,即先阻塞的线程先访问队列。
  • 非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程均可以争夺访问队列的资格。有可能先阻塞的线程最后才访问访问队列。
  • 公平性会下降吞吐量。

14.2 ArrayBlockingQueue使用示例

建立两个积木:三角形、四边形,而后添加到队列:

BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四边形", "B");
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(100, true);
arrayBlockingQueue.add(buildingBlock1);
arrayBlockingQueue.add(buildingBlock2);
复制代码

14.3 ArrayBlockQueue实现了哪些接口

  • Deque接口 - 可用于双端队列

十5、链表阻塞LinkedBlockingQueue类

15.1 理解LinkedBlockingQueue

LinkedBlockingQueue原理

  • LinkedBlockingQueue具备单链表和有界阻塞队列的功能。
  • 队列慢时插入操做被阻塞,队列空时,移除操做被阻塞。
  • 默认和最大长度为Integer.MAX_VALUE,至关于无界(值很是大:2^31-1)。

15.2 LinkedBlockingQueue使用示例

LinkedList linkedList1 = new LinkedList();
linkedList1.add("A");
linkedList1.add("B");
linkedList1.add("C");
复制代码

15.3 LinkedBlockingQueue的应用场景

  • 吞吐量一般要高于ArrayBlockingQueue。建立线程池时,参数runnableTaskQueue(任务队列),用于保存等待执行的任务的阻塞队列能够选择LinkedBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

15.4 LinkedBlockingQueue实现了哪些接口

  • LinkedBlockingQueue继承了 BlockingQueue类,可做为阻塞队列使用
  • LinkedBlockingQueue继承了 AbstractQueue抽象类,具备队列的功能。
  • LinkedBlockingQueue实现了 java.io.Serializable 接口,便可支持序列化,能经过序列化去传输。

十6、双向阻塞LinkedBlockingDeque类

16.1 理解LinkedBlockingDeque类

LinkedBlockingDeque原理图

  • 由链LinkedBlockingDeque = 阻塞队列+链表+双端访问
  • 线程安全。
  • 多线程同时入队时,因多了一端访问入口,因此减小了一半的竞争。
  • 默认容量大小为Integer.MAX_VALUE。可指定容量大小。

16.2 LinkedBlockingDeque的应用场景

LinkedBlockingDeque能够用在“工做窃取“模式中。

工做窃取算法:某个线程比较空闲,从其余线程的工做队列中的队尾窃取任务来帮忙执行。

16.3 LinkedBlockingDeque实现了哪些接口

  • LinkedBlockingDeque继承了 BlockingDeque类,可做为阻塞队列使用
  • LinkedBlockingDeque继承了 AbstractQueue抽象类,具备队列的功能。
  • LinkedBlockingDeque实现了 java.io.Serializable 接口,便可支持序列化,能经过序列化去传输。

十7、链表阻塞LinkedTransferQueue类

17.1 理解LinkedTransferQueue类

LinkedTransferQueue原理图

LinkedTransferQueue = 阻塞队列+链表结构+TransferQueue

以前咱们讲“使命必达TransferQueue接口时已经介绍过了TransferQueue接口 ,因此LinkedTransferQueue接口跟它类似,只是加入了阻塞插入和移除的功能,以及结构是链表结构。

以前的TransferQueue也讲到了3个案例来讲明TransferQueue的原理,你们能够回看TransferQueue。

17.2 LinkedTransferQueue接口比其余阻塞队列多了5个方法

  • transfer(E e)
  • tryTransfer(E e)
  • tryTransfer(E e, long timeout, TimeUnit unit)
  • getWaitingConsumerCount()
  • hasWaitingConsumer()

17.3 LinkedTransferQueue代码示例

  • 建立一个LinkedTransferQueue,生产者1 依次往队列中添加 A、B、C

生产者1 依次往队列中添加 A、B、C

  • 生产者2 依次往队列中添加 D、E、F

生产者2 依次往队列中添加 D、E、F

  • 消费者依次从队列首部开始消费元素,每次消费前,先sleep 2s,来演示transfer方法是否进行了等待。

消费者消费元素

  • 运行结果
生产者1     transfer A 
生产者2     transfer D 

2s后...
  
消费者      take A
生产者1     put B 
 
2s后...
     
消费者      take D
生产者2     transfer E 
    
2s后...
  
消费者      take B
生产者1     transfer C 
复制代码
  • 代码执行结果分析

(1)首先生产者线程1和2 调用transfer方法来传输A和D,发现没有消费者线程接收,因此被阻塞。

(2)消费者线程过了2s后将A拿走了,而后生产者1 被释放继续执行,传输元素B,发现又没有消费者消费,因此进行了等待。

(3)消费者线程过了2s后,将排在队列首部的D元素拿走,生产者2继续往下执行,传输元素E,发现没有消费者,因此进行了等待。

(4)消费者线程过了2s后,将排在队列首部的B元素拿走,生产者1传输C元素,等待消费者拿走。

(5)消费者再也不消费了,因此生产者1和生产者2都被阻塞了,元素C和,元素E都没有被拿走,并且生产者2的F元素尚未开始传输,由于在等待元素D被拿走。

(6)看下队列里面确实有C和E元素,并且E排在队列的首部。

队列里面的元素

17.4 LinkedTransferQueue实现了哪些接口

  • LinkedBlockingDeque继承了 BlockingQeque类,可做为阻塞队列使用
  • LinkedBlockingDeque继承了 AbstractQueue抽象类,具备队列的功能。

十8、传球好手SynchronousQueue类

18.1 理解SynchronousQueue类

SynchronousQueue原理图

  • 我称SynchronousQueue为”传球好手“。想象一下这个场景:小明抱着一个篮球想传给小花,若是小花没有将球拿走,则小明是不能再拿其余球的。

  • SynchronousQueue负责把生产者产生的数据传递给消费者线程。

  • SynchronousQueue自己不存储数据,调用了put方法后,队列里面也是空的。

  • 每个put操做必须等待一个take操做完成,不然不能添加元素。

  • 适合传递性场景。

  • 性能高于ArrayBlockingQueue和LinkedBlockingQueue。

18.2 SynchronousQueue示例

咱们建立了两个线程,一个线程用于生产,一个线程用于消费

  • 生产的线程依次put A、B、C三个值

生产的线程依次put A、B、C三个值

  • 消费线程使用take来消费阻塞队列中的内容,每次消费前,等待5秒

消费线程每隔5s调用take方法

  • 运行结果
t1     put A 
t2     take A 

5秒后...

t1     put B 
t2     take B 

5秒后...

t1     put C 
t2     take C 
复制代码

小结:说明生产线程执行put第一个元素"A" 操做后,须要等待消费者线程take完“A”后,才能继续往下执行代码。

18.3 SynchronousQueue应用场景

  • 吞吐量一般要高于LinkedBlockingQueue。建立线程池时,参数runnableTaskQueue(任务队列),用于保存等待执行的任务的阻塞队列能够选择SynchronousQueue。静态工厂方法Executors.newCachedThreadPool()使用了这个队列

18.4 SynchronousQueue和LinkedTransferQueue的区别

  • SynchronousQueue 不存储元素,而LinkedTransferQueue存储元素。
  • SynchronousQueue 队列里面没有元素,而LinkedTransferQueue能够有多个存储在队列等待传输。
  • LinkedTransferQueue还支持若传输不了,则丢到队列里面去。
  • LinkedTransferQueue还支持若超过必定时间传输不了,则丢到队列里面去。

十9、优先级阻塞PriorityBlockingQueue类

19.1 理解PriorityBlockQueue类

PriorityBlockQueue的原理图

  • PriorityBlockQueue = PriorityQueue + BlockingQueue
  • 以前咱们也讲到了PriorityQueue的原理,支持对元素排序。
  • 元素默认天然排序。
  • 能够自定义CompareTo()方法来指定元素排序规则。
  • 能够经过构造函数构造参数Comparator来对元素进行排序。

19.2 PriorityBlockQueue实现了哪些接口

  • LinkedBlockingQueue继承了 BlockingQueue接口,可做为阻塞队列使用
  • LinkedBlockingQueue继承了 AbstractQueue抽象类,具备队列的功能。
  • LinkedBlockingQueue实现了 java.io.Serializable 接口,便可支持序列化,能经过序列化去传输。

二10、延时阻塞DelayQueue类

20.1 理解DelayQueue

DelayQueue原理图

  • DelayQueue = Delayed + BlockingQueue。队列中的元素必须实现Delayed接口。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
复制代码
  • 在建立元素时,能够指定多久能够从队列中获取到当前元素。只有在延时期满才能从队列中获取到当前元素。

20.2 源码解析

  • 添加元素时,指定延时多久能够从队列中获取元素
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e);
}
复制代码
  • 获取元素的方法poll须要等待延时时间过了才能获取到元素
if (first == null || first.getDelay(NANOSECONDS) > 0)
    return null;
else
    return q.poll();
复制代码

poll方法

20.3 应用场景

  • 缓存系统的设计:能够用DelayQueue保存缓存元素的有效期。而后用一个线程循环的查询DelayQueue队列,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。

  • 定时任务调度:使用DelayQueue队列保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行。好比Java中的TimerQueue就是使用DelayQueue实现的。

20.4 DelayQueue实现了哪些接口

  • DelayQueue实现了 BlockingQueue接口,可做为阻塞队列使用

这一篇花了不少心思在上面,看官方英文文档、画原理图、写demo代码,排版。这恐怕是市面上最全最细讲解Queue的。

mark

你好,我是悟空哥「7年项目开发经验,全栈工程师,开发组长,超喜欢图解编程底层原理」。正在编写两本PDF,分别是 一、Spring Cloud实战项目(佳必过),二、Java并发必知必会。我还手写了2个小程序,Java刷题小程序,PMP刷题小程序,点击个人公众号菜单打开!另外有111本架构师资料以及1000道Java面试题,都整理成了PDF,能够关注公众号 「悟空聊架构」 回复 悟空 领取优质资料。

二维码

「转发->在看->点赞->收藏->评论!!!」 是对我最大的支持!

《Java并发必知必会》系列:

1.反制面试官 | 14张原理图 | 不再怕被问 volatile!

2.程序员深夜惨遭老婆鄙视,缘由竟是CAS原理太简单?

3.用积木讲解ABA原理 | 老婆竟然又听懂了!

4.全网最细 | 21张图带你领略集合的线程不安全

5.5000字 | 24张图带你完全理解Java中的21种锁

相关文章
相关标签/搜索