title: 死磕Java队列
date: 2020-05-18 15:53:00
categories: 多线程,Java,Queue,队列
description: Java
1. 基本概念
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
-
分类:队列除了保证队列中的元素有序之外,它在基本构造上还在差异,主要体现在 容量 、 阻塞 、 线程安全三个方面。
-
队列容量:从队列的大小以及容量来说,分为 有界队列、无界队列 。
-
阻塞:从现场阻塞的特征来说,分为 阻塞队列、非阻塞队列,这决定了队列的吞吐性能。
-
线程安全:从线程安全来说,分为 线程安全、非线程安全
-
-
特点:队列这种最先进去的数据最先被取来,即
先进先出
的结构,统称为First In First Out
,简称FIFO
。与栈类似,队列中可以操作数据的位置也有一定的限制。在栈中,数据的添加和删除都在同一端进行,而在队列中则分别是在两端进行的。队列也不能直接访问位于中间的数据,必须通过出队操作将目标数据变成首位后才能访问。类似于现实中排队时的队列(队尾进,队头出),插入元素的一端称为队尾,删除(取出)元素的一端称为队头。分别对应于入队和出队操作。
队列除了上述差异,有的地方还把顺序队列、链式队列以及循环队列这三种队列单独拿出来说。
1.1. 非阻塞队列与阻塞队列
1.1.1. 阻塞队列
支持两个附加操作的队列。这两个附加的操作支持阻塞的 插入
和 移除
方法。具体表现在:
- 队列的容量已满:队列会阻塞插入元素的线程,直到队列不满
- 队列中元素为空:获取元素的线程会等待队列变为非空
生产数据时,若队列已满,那么生产线程会暂停下来,直到队列中有可以存放数据的地方,才会继续工作;而当我们的消费者向队列中获取数据时,若队列为空,则消费者线程会暂停下来,直到容器中有元素出现,才能进行获取操作。
1.1.2. 非阻塞队列
- 队列的容量已满:插入元素抛出异常
- 队列中元素为空:获取元素的为空
1.2. 有界队列与无界队列
-
有界队列:队列对索要存储元素的上限设置有固定值。队列的容量,队列的容量决定了该队列保存元素的数量上限,这在某些特定场景下,使用 有界队列 可以规避无休止的元素添加而造成内存资源上的开销。
-
无界队列:队列对索要存储元素的没有设置固定大小,一般理论上的上线就是
Integer.MAX_VALUE
的值,但是从使用者的体验上,就相当于 “无界”。
2. 使用场景
JAVA
中的队列是在 JDK1.5
中被引入,它是一种数据结构,遵循原则就是 FIFO
,即 先进先出
。它的存在就是是为了解决 削峰填谷
,给队列下游减轻压力,将生产速度较快的地方进行减速,以便于适应下游的处理速度。
3. 队列实现
线性表有 顺序存储
和 链式存储
,队列作为一种特殊的线性表,也同样存在这两种存储方式。
3.1. 顺序队列
用数组存储队列,即利用一组地址连续的存储单元依次存放队列中的元素。为了避免当只有一个元素时,队头和队尾重合使得处理变得麻烦,所以引入两个指针(头尾指针):front指针指向队头元素,rear指针指向队尾元素的下一个位置。初始化时的头尾指针,初始值均为0。入队时尾指针rear加1,出队时头指针front加1,头尾指针相等时队列为空。
- 初始化为空队列,头尾指针相等
- 每一次有元素入队,尾指针加1,头指针不变,尾指针始终指向队尾元素的下一位
- 每一次有元素出队,则尾指针不变,头指针加1
- 最后所有元素出队,则头指针再次和尾指针相等,说明队列空了
- 当尾指针已经指向了队列的最后一个位置的下一位置时,若再有元素入队,就会发生“溢出”。
3.2. 循环队列
前一章节说到顺序队列的 “假溢出” 问题:队列的存储空间未满,却发生了溢出。比如尾指针现在虽然已经指向了最后一个位置的下一位置,但是之前队头也删除了一些元素,那么头指针经历若干次的加1之后,留出了很多空位置,但是顺序队列还在傻乎乎的以为再有元素入队就溢出呢!肯定不合理。故循环队列诞生!
所以解决"假溢出"的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。将新元素插入到第一个位置上,入队和出队仍按先进先出的原则进行,操作效率高,空间利用率高。
3.3. 链队列
链队列的就是一个操作受限的单向链表,只不过它只能尾进头出而已,我们把它简称为链队列。链式队列和单向链表比就多了两个指针,头指针和尾指针。
相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。而且可动态分配空间,不需要预先分配大量存储空间。适合处理用户排队等待的情况。
3.4. 顺序队列和链式队列的比较
- 顺序队列是用数组实现的,首指针在出队的时候移动,尾指针在入队的时候移动,需要考虑队列为空和队列为满的两种情况
- 链式队列是用链表实现的,首指针不移动始终指向头节点,尾指针在入队的时候移动,只考虑队列为空的情况(不用考虑满是因为链表长度在程序运行过程中可以不断增加,只要存储空间够malloc就能申请内存空间来存放节点)
4. Java常用队列
日常使用中一些常用的 JAVA
队列的列表,我们按照是否阻塞、是否线程安全、是否有界作为划分,具体如下:
类名 | 是否阻塞 | 是否线程安全 | 是否有界 | 特点 |
---|---|---|---|---|
LinkedList | 非阻塞 | 线程安全 | 可选有界 | 基于链表 |
ArrayBlockingQueue | 阻塞 | 线程安全 | 有界 | 基于数组的有界队列 |
LinkedBlockingQueue | 阻塞 | 线程安全 | 可选有界 | 基于链表的无界队列 |
ConcurrentLinkedQueue | 非阻塞 | 线程安全 | 无界 | 基于链表 线程安全的队列 |
PriorityBlockingQueue | 阻塞 | 线程安全 | 无界 | 基于优先次序的无界队列 |
PriorityQueue | 非阻塞 | 非线程安全 | 无界 | |
SynchronousQueue | 阻塞 | 线程安全 | 无数据队列 | 内部没有容器的队列 较特别 |
LinkedBlockingQueue | 阻塞 | 线程安全 | 无界 | |
DelayQueue | 阻塞 | 线程安全 | 无界 | 基于时间优先级的队列 |
LinkedTrmsferQueue | 阻塞 | 线程安全 | 无界 | 基于链表 |