在讲《21张图讲解集合的线程不安全》那一篇,我留了一个彩蛋,就是Queue(队列)尚未讲,此次咱们重点来看看Java中的Queue家族,总共涉及到18种Queue。这篇恐怕是市面上最全最细 讲解Queue的。java
本篇主要内容以下:程序员
帮你总结好的阻塞队列:面试
hi,你们好,个人英文名叫Queue
,中文名叫队列
,不管现实生活中仍是计算机的世界中,我都是一个很重要的角色哦~算法
我是一种数据结构
,你们能够把我想象成一个数组,元素从个人一头进入、从另一头出去,称为FIFO原则(先进先出原则)。编程
我还有两个亲兄弟:List
(列表)、Set
(集),他们都是Collection
的儿子,我还有一个远房亲戚:Map
(映射)。他们都是java.util
包这个你们庭的成员哦~小程序
18种队列分为三大类: 接口、抽象类、普通类。数组
弄清楚下面的继承实现关系对后面理解18种队列有很大帮助。缓存
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
抽象类注意:安全
Queue接口是一种Collection,被设计用于处理以前临时保存在某处的元素。markdown
除了基本的Collection操做以外,队列还提供了额外的插入、提取和检查操做。每一种操做都有两种形式:若是操做失败,则抛出一个异常;若是操做失败,则返回一个特殊值(null或false,取决因而什么操做)。
队列一般是以FIFO(先进先出)的方式排序元素,可是这不是必须的。
只有优先级队列能够根据提供的比较器对元素进行排序或者是采用正常的排序。不管怎么排序,队列的头将经过调用remove()或poll()方法进行移除。在FIFO队列种,全部新的元素被插入到队尾。其余种类的队列可能使用不一样的布局来存放元素。
每一个Queue必须指定排序属性。
总共有3组方法,每一组方法对应两个方法。以下图所示:
动做 | 抛异常 | 返回特殊值 |
---|---|---|
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方法的特殊返回值,以说明队列不包含元素。
(1)Deque概念: 支持两端元素插入和移除的线性集合。名称deque
是双端队列的缩写,一般发音为deck
。大多数实现Deque的类,对它们包含的元素的数量没有固定的限制的,支持有界和无界。
(2)Deque方法说明:
**说明: **
该列表包含包含访问deque两端元素的方法,提供了插入,移除和检查元素的方法。
这些方法种的每一种都存在两种形式:若是操做失败,则会抛出异常,另外一种方法返回一个特殊值(null或false,取决于具体操做)。
插入操做的后一种形式专门设计用于容量限制的Deque实现,大多数实现中,插入操做不能失败,因此能够用插入操做的后一种形式。
Deque接口扩展了Queue接口,当使用deque做为队列时,做为FIFO。元素将添加到deque的末尾,并从头开始删除。
做为FIFO时等价于Queue的方法以下表所示:
好比Queue的add方法和Deque的addLast方法等价。
Deque也能够用做LIFO(后进先出)栈,这个接口优于传统的Stack类。看成为栈使用时,元素被push到deque队列的头,而pop也是从队列的头pop出来。
Stack(栈)的方法正好等同于Deque的以下方法:
注意:peek方法不管是做为栈仍是队列,都是从队列的检测队列的头,返回最早加入的元素。好比第一次put 100,第二次put 200,则peek返回的是100。以下图所示:
AbstractQueue是一个抽象类,继承了Queue接口,提供了一些Queue操做的骨架实现。
方法add、remove、element方法基于offer、poll和peek。也就是说若是不能正常操做,则抛出异常。咱们来看下AbstactQueue是怎么作到的。
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
复制代码
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
复制代码
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
复制代码
注意:
ArrayBlockingQueue
类、LinkendBlockingQueue
类、LinkedBlockingDeque
类、LinkedTransferQueue
类、SynchronousQueue
类、PriorityBlockQueue
类、DelayQueue类
继承 了AbstractQueue
抽象类PriorityQueue
类和ConcurrentLinkedQueue
类继承 了AbstractQueue
抽象类(1)BlockingQueue(阻塞队列)也是一种队列,支持阻塞的插入和移除方法。
(3)阻塞的插入:当队列满时,队列会阻塞插入元素的线程,直到队列不满。
(4)阻塞的移除:当队列为空,获取元素的线程会等待队列变为非空。
(5)应用场景:生产者和消费者,生产者线程向队列里添加元素,消费者线程从队列里移除元素,阻塞队列时获取和存放元素的容器。
(6)为何要用阻塞队列:生产者生产和消费者消费的速率不同,须要用队列来解决速率差问题,当队列满了或空的时候,则须要阻塞生产或消费动做来解决队列满或空的问题。
线程A往阻塞队列(Blocking Queue)中添加
元素,而线程B从阻塞队列中移除
元素。
BlockingQueue接口的10个核心方法:
10个核心方法总结以下:
有三大类操做:插入、移除、检查。
IllegalStateException
- 队列满了ClassCastException
- 添加的元素类型不匹配NullPointerException
- 添加的元素为nullIllegalArgumentException
- 添加的元素某些属性不匹配NoSuchElementException
- 若是这个队列是空的是阻塞队列BlockingQueue
和双向队列Deque
接口的结合。有以下方法:
示例:
尝试执行如下方法:
LinkedBlockingDeque queue = new LinkedBlockingDeque();
queue.addFirst("test1");
queue.addFirst(300);
queue.addLast("400");
复制代码
最后队列中的元素顺序以下:
300, test1, 400。
先添加了test1放到队列的头部,而后在头部的前面放入300,因此300在最前面,成为头部,而后将400放入队列的尾部,因此最后的结果是300, test1, 400。
若是有消费者正在获取元素,则将队列中的元素传递给消费者。若是没有消费者,则等待消费者消费。我把它称做使命必达队列,必须将任务完成才能返回。
transfer(E e)
原理以下图所示:
tryTransfer(E e)
tryTransfer(E e, long timeout, TimeUnit unit)
getWaitingConsumerCount()
hasWaitingConsumer()
PriorityQueue是一个支持优先级的无界阻塞队列。
默认天然顺序升序排序。
能够经过构造参数Comparator来对元素进行排序。
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
复制代码
public Comparator<? super E> comparator() {
return comparator;
}
复制代码
Arrays.sort(pq.toArray)
。offer
、add
)和出列( poll
、remove()
)的时间复杂度O(log(n))。咱们来看下节点类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;
}
}
复制代码
1.LinkedList的增长和删除效率相对较高,而查找和修改的效率相对较低。
2.如下状况建议使用ArrayList
3.如下状况建议使用LinkedList
LinkedList不是线程安全的,因此可使用以下方式保证线程安全。
List list = Collections.synchronizedList(new LinkedList<>());
复制代码
LinkedList 继承了 AbstractSequentialList 类。
LinkedList 实现了 Queue 接口,可做为队列使用。
LinkedList 继承了 AbstractQueue抽象类,具备队列的功能。
LinkedList 实现了 List 接口,可进行列表的相关操做。
LinkedList 实现了 Deque 接口,可做为双向队列使用。
LinkedList 实现了 Cloneable 接口,可实现克隆。
LinkedList 实现了 java.io.Serializable 接口,便可支持序列化,能经过序列化去传输。
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
BuildingBlockWithName buildingBlock = new BuildingBlockWithName("三角形", "A");
concurrentLinkedQueue.add(buildingBlock);
复制代码
建立一个ArrayDeque,往arrayDeque队尾添加元素。
ArrayDeque arrayDeque = new ArrayDeque();
for (int i = 0; i < 50; i++) {
arrayDeque.add(buildingBlock); // add方法等价于addLast方法
}
复制代码
建立两个积木:三角形、四边形,而后添加到队列:
BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四边形", "B");
ConcurrentLinkedDeque concurrentLinkedDeque = new ConcurrentLinkedDeque();
concurrentLinkedDeque.addFirst(buildingBlock1);
concurrentLinkedDeque.addLast(buildingBlock2);
//结果:顺序:三角形、四边形
复制代码
建立两个积木:三角形、四边形,而后添加到队列:
BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四边形", "B");
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(100, true);
arrayBlockingQueue.add(buildingBlock1);
arrayBlockingQueue.add(buildingBlock2);
复制代码
LinkedList linkedList1 = new LinkedList();
linkedList1.add("A");
linkedList1.add("B");
linkedList1.add("C");
复制代码
LinkedBlockingDeque能够用在“工做窃取“模式中。
工做窃取算法
:某个线程比较空闲,从其余线程的工做队列中的队尾窃取任务来帮忙执行。
LinkedTransferQueue = 阻塞队列+链表结构+TransferQueue
以前咱们讲“使命必达TransferQueue接口时已经介绍过了TransferQueue接口 ,因此LinkedTransferQueue接口跟它类似,只是加入了阻塞插入和移除的功能,以及结构是链表结构。
以前的TransferQueue也讲到了3个案例来讲明TransferQueue的原理,你们能够回看TransferQueue。
生产者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排在队列的首部。
我称SynchronousQueue为”传球好手“。想象一下这个场景:小明抱着一个篮球想传给小花,若是小花没有将球拿走,则小明是不能再拿其余球的。
SynchronousQueue负责把生产者产生的数据传递给消费者线程。
SynchronousQueue自己不存储数据,调用了put方法后,队列里面也是空的。
每个put操做必须等待一个take操做完成,不然不能添加元素。
适合传递性场景。
性能高于ArrayBlockingQueue和LinkedBlockingQueue。
咱们建立了两个线程,一个线程用于生产,一个线程用于消费
t1 put A
t2 take A
5秒后...
t1 put B
t2 take B
5秒后...
t1 put C
t2 take C
复制代码
小结:说明生产线程执行put第一个元素"A" 操做后,须要等待消费者线程take完“A”后,才能继续往下执行代码。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
复制代码
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
复制代码
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll();
复制代码
缓存系统的设计:能够用DelayQueue保存缓存元素的有效期。而后用一个线程循环的查询DelayQueue队列,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
定时任务调度:使用DelayQueue队列保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行。好比Java中的TimerQueue就是使用DelayQueue实现的。
这一篇花了不少心思在上面,看官方英文文档、画原理图、写demo代码,排版。这恐怕是市面上最全最细讲解Queue的。
❝
你好,我是
悟空哥
,「7年项目开发经验,全栈工程师,开发组长,超喜欢图解编程底层原理」。正在编写两本PDF,分别是 一、Spring Cloud实战项目(佳必过),二、Java并发必知必会。我还手写了2个小程序
,Java刷题小程序,PMP刷题小程序,点击个人公众号菜单打开!另外有111本架构师资料以及1000道Java面试题,都整理成了PDF,能够关注公众号 「悟空聊架构」 回复悟空
领取优质资料。❞
「转发->在看->点赞->收藏->评论!!!」 是对我最大的支持!
《Java并发必知必会》系列: