title: 死磕Java队列
date: 2020-05-18 15:53:00
categories: 多线程,Java,Queue,队列
description: Java

1. 基本概念

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。

  • 分类:队列除了保证队列中的元素有序之外,它在基本构造上还在差异,主要体现在 容量 、 阻塞 、 线程安全三个方面。

    • 队列容量:从队列的大小以及容量来说,分为 有界队列无界队列

    • 阻塞:从现场阻塞的特征来说,分为 阻塞队列非阻塞队列,这决定了队列的吞吐性能。

    • 线程安全:从线程安全来说,分为 线程安全非线程安全

  • 特点:队列这种最先进去的数据最先被取来,即 先进先出 的结构,统称为 First In First Out,简称 FIFO。与栈类似,队列中可以操作数据的位置也有一定的限制。在栈中,数据的添加和删除都在同一端进行,而在队列中则分别是在两端进行的。队列也不能直接访问位于中间的数据,必须通过出队操作将目标数据变成首位后才能访问。类似于现实中排队时的队列(队尾进,队头出),插入元素的一端称为队尾,删除(取出)元素的一端称为队头。分别对应于入队和出队操作。

队列除了上述差异,有的地方还把顺序队列、链式队列以及循环队列这三种队列单独拿出来说。

20230808102326

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,头尾指针相等时队列为空。

  • 初始化为空队列,头尾指针相等

20230808103200

  • 每一次有元素入队,尾指针加1,头指针不变,尾指针始终指向队尾元素的下一位

20230808103227

  • 每一次有元素出队,则尾指针不变,头指针加1

20230808103256

  • 最后所有元素出队,则头指针再次和尾指针相等,说明队列空了

20230808103326

  • 当尾指针已经指向了队列的最后一个位置的下一位置时,若再有元素入队,就会发生“溢出”。

20230808103401

3.2. 循环队列

前一章节说到顺序队列的 “假溢出” 问题:队列的存储空间未满,却发生了溢出。比如尾指针现在虽然已经指向了最后一个位置的下一位置,但是之前队头也删除了一些元素,那么头指针经历若干次的加1之后,留出了很多空位置,但是顺序队列还在傻乎乎的以为再有元素入队就溢出呢!肯定不合理。故循环队列诞生!

所以解决"假溢出"的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。将新元素插入到第一个位置上,入队和出队仍按先进先出的原则进行,操作效率高,空间利用率高。

20230808103622

3.3. 链队列

链队列的就是一个操作受限的单向链表,只不过它只能尾进头出而已,我们把它简称为链队列。链式队列和单向链表比就多了两个指针,头指针和尾指针。

相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。而且可动态分配空间,不需要预先分配大量存储空间。适合处理用户排队等待的情况。

20230808103925

20230808103943

3.4. 顺序队列和链式队列的比较

  • 顺序队列是用数组实现的,首指针在出队的时候移动,尾指针在入队的时候移动,需要考虑队列为空和队列为满的两种情况
  • 链式队列是用链表实现的,首指针不移动始终指向头节点,尾指针在入队的时候移动,只考虑队列为空的情况(不用考虑满是因为链表长度在程序运行过程中可以不断增加,只要存储空间够malloc就能申请内存空间来存放节点)

4. Java常用队列

日常使用中一些常用的 JAVA 队列的列表,我们按照是否阻塞、是否线程安全、是否有界作为划分,具体如下:

类名 是否阻塞 是否线程安全 是否有界 特点
LinkedList 非阻塞 线程安全 可选有界 基于链表
ArrayBlockingQueue 阻塞 线程安全 有界 基于数组的有界队列
LinkedBlockingQueue 阻塞 线程安全 可选有界 基于链表的无界队列
ConcurrentLinkedQueue 非阻塞 线程安全 无界 基于链表 线程安全的队列
PriorityBlockingQueue 阻塞 线程安全 无界 基于优先次序的无界队列
PriorityQueue 非阻塞 非线程安全 无界
SynchronousQueue 阻塞 线程安全 无数据队列 内部没有容器的队列 较特别
LinkedBlockingQueue 阻塞 线程安全 无界
DelayQueue 阻塞 线程安全 无界 基于时间优先级的队列
LinkedTrmsferQueue 阻塞 线程安全 无界 基于链表
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐