JAVA集合面试分享十一:JAVA集合框架详解之Queue-阶段(四)

目录

一、Queue的简介

二、Queue的特点

三、Queue的优缺点

四、Queue的使用场景

五、Queue的分类

        1、阻塞与非阻塞的概念

        (1)、非阻塞队列中的几个主要方法

        (2)、阻塞队列中的几个主要方法

        2、阻塞队列

1. ArrayBlockingQueue(公平、非公平)

2. LinkedBlockingQueue(两个独立锁提高并发)

3. PriorityBlockingQueue(compareTo 排序实现优先)

4. DelayQueue(缓存失效、定时任务 )

5. SynchronousQueue(不存储数据、可用于传递数据)

6. LinkedTransferQueue

7. LinkedBlockingDeque

        3、非阻塞队列

1、ConcurrentLinkedQueue(线程安全)

2、ConcurrentLinkedDeque(线程安全)

3、PriorityQueue(线程不安全)

4、LinkedList

5、ArrayDeque

六、Queue常见问题

七、总结


先给大家看一张图,有个大致了解:

再来看下Queue的继承关系:

一、Queue的简介

基本上,一个队列就是一个先入先出(FIFO)的数据结构;

是一种常见的线性数据结构,在Java中用于存储和操作元素序列。它基于先进先出(First-In-First-Out, FIFO)原则,即最早入队的元素首先出队。只能在队尾添加元素,在队头删除元素。

二、Queue的特点

  • 先进先出:最早添加到队列中的元素将首先被移除。
  • 限制访问:只能通过队头和对尾对元素进行访问或修改,不允许随机访问。
  • 动态大小:与数组相比,使用动态内存分配可以动态调整队列大小。

三、Queue的优缺点

        优点:
        简单易用:实现简单明了,并且提供了基本操作如入队、出队等。
        节省空间:仅需要保存实际放入队列内的数据。
        缺点:
        容量限制:因为底层使用数组或链表来实现,所以可能受到内存容量限制。
        难以检索和更新非头部/尾部数据: 要查找、更新或删除非头部/尾部位置上的元素会变得复杂且低效。

四、Queue的使用场景

  • 需要按照特定顺序管理和操作元素的场景。
  • 常用于任务调度、消息传递、广度优先搜索等。

五、Queue的分类

        1、阻塞与非阻塞的概念

        其实差别都不大,阻塞和非阻塞的区别在于阻塞队列有put和take方法进行阻塞,而非阻塞队列则没有这两个方法,同时poll和offer方法也不提供超时参数设定。

        (1)、非阻塞队列中的几个主要方法

                add(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队                  列已满),则会抛出异常;

                remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则                    会抛出异常;

                offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即                    队列已满),则返回false;

                poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null;

                peek():获取队首元素,若成功,则返回队首元素;否则返回null

        对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果。注意,非阻塞队列中的方法都没有进行同步措施。

        (2)、阻塞队列中的几个主要方法

        阻塞队列包括了非阻塞队列中的大部分方法,上面列举的5个方法在阻塞队列中都存在,但是要注意这5个方法在阻塞队列中都进行了同步措施。

        除此之外,阻塞队列提供了另外4个非常有用的方法:

          put(E e)

          take()

          offer(E e,long timeout, TimeUnit unit)

          poll(long timeout, TimeUnit unit)

        这四个方法的理解:

          put方法用来向队尾存入元素,如果队列满,则等待;

          take方法用来从队首取元素,如果队列为空,则等待;

          offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;

          poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;

        2、阻塞队列
  •         ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
  •         LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
  •         PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
  •         DelayQueue:使用优先级队列实现的无界阻塞队列。
  •         SynchronousQueue:不存储元素的阻塞队列。
  •         LinkedTransferQueue:由链表结构组成的无界阻塞队列。
  •         LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
1. ArrayBlockingQueue(公平、非公平)

用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当

队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。

2. LinkedBlockingQueue(两个独立锁提高并发)

基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,此队列按照先进先出(FIFO)的原则对元素进行排序。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者

端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE)。

3. PriorityBlockingQueue(compareTo 排序实现优先)

是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现compareTo()方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue 时,指定构造参数Comparator 来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。

4. DelayQueue(缓存失效、定时任务 )

它是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将 DelayQueue 运用在以下应用场景:

缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从 DelayQueue 中获取元素时,表示缓存有效期到了。
定时任务调度:使用 DelayQueue 保存当天将会执行的任务和执行时间,一旦从DelayQueue 中获取到任务就开始执行,从比如 TimerQueue 就是使用 DelayQueue 实现的。

5. SynchronousQueue(不存储数据、可用于传递数据)

它是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给另外一个线程使用, SynchronousQueue 的吞吐量高于 LinkedBlockingQueue 和ArrayBlockingQueue。

6. LinkedTransferQueue

它是一个由链表结构组成的无界阻塞 TransferQueue 队列。相对于其他阻塞队列,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。

transfer 方法:如果当前有消费者正在等待接收元素(消费者使用 take()方法或带时间限制的poll()方法时),transfer 方法可以把生产者传入的元素立刻 transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer 方法会将元素存放在队列的 tail 节点,并等到该元素被消费者消费了才返回。
tryTransfer 方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回 false。和 transfer 方法的区别是 tryTransfer 方法无论消费者是否接收,方法立即返回。而 transfer 方法是必须等到消费者消费了才返回。对于带有时间限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回 false,如果在超时时间内消费了元素,则返回 true。

7. LinkedBlockingDeque

它是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast 等方法,以 First 单词结尾的方法,表示插入,获取(peek)或移除双端队列的第一个元素。以 Last 单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另外插入方法 add 等同于 addLast,移除方法 remove 等效于 removeFirst。但是 take 方法却等同于 takeFirst,不知道是不是 Jdk 的 bug,使用时还是用带有 First 和 Last 后缀的方法更清楚。

        3、非阻塞队列
  •         ConcurrentLinkedQueue(线程安全)
  •         ConcurrentLinkedDeque(线程安全)
  •         PriorityQueue(线程不安全)
  •         LinkedList
  •         ArrayDeque
1、ConcurrentLinkedQueue(线程安全)

ConcurrentLinkedQueue的实现原理主要是基于链表CAS操作。它是一个基于链接节点的、无界的线程安全队列。在其内部,它使用一个私有静态类Node<E>来封装元素,并保存下一个节点的引用。这个Node<E>类中的itemnext字段都被声明为volatile,以确保多线程环境下的可见性。

ConcurrentLinkedQueue的线程安全性是通过非阻塞算法实现的,特别是使用CAS(Compare-And-Swap)操作。CAS操作是一种原子操作,用于在多线程环境中无锁地保证数据的一致性和完整性。

简单来说,当我们要在ConcurrentLinkedQueue中添加或移除元素时,它会使用CAS操作来确保节点引用的更改是原子的。这样,即使在多线程环境中,也能保证队列的状态始终保持一致,而不会出现线程不安全的情况。

需要注意的是,由于ConcurrentLinkedQueue是非阻塞的,当多个线程同时尝试修改队列时,某些操作可能会失败,并需要重试。因此,它通常适用于高并发场景,其中线程之间的争用不是很激烈,或者失败的操作可以快速地重试。

2、ConcurrentLinkedDeque(线程安全)

ConcurrentLinkedDeque的实现原理与ConcurrentLinkedQueue相似,它也是基于链表和CAS操作来实现的。但是,ConcurrentLinkedDeque是一个双端队列(Double-Ended Queue,简称DEQUE),这意味着它允许在队列的两端进行添加和移除元素。

在其内部,ConcurrentLinkedDeque也使用了一个私有静态类来封装节点,这个节点类包含了指向前一个节点和后一个节点的引用,从而实现了双端操作。

同样地,为了保证线程安全,ConcurrentLinkedDeque也采用了CAS操作进行原子性修改。这使得在多线程环境下,队列的状态能够保持一致,防止线程不安全情况的发生。

总结起来,ConcurrentLinkedDeque的实现原理是通过链表结构和CAS操作来实现一个线程安全的双端队列,它可以在队列的两端进行高效的添加和移除元素操作。

3、PriorityQueue(线程不安全)

PriorityQueue的实现原理是基于**优先级堆(Priority Heap)**的。优先级队列中的元素按照它们的优先级进行排序,优先级最高的元素最先得到处理。

PriorityQueue内部使用一个堆数据结构来管理元素。堆是一种完全二叉树,它分为最大堆和最小堆。在最大堆中,父节点的值总是大于或等于其子节点的值;而在最小堆中,父节点的值总是小于或等于其子节点的值。PriorityQueue是基于最小堆实现的。

堆结构能够在对数时间复杂度内完成插入和删除操作,这使得PriorityQueue能够在处理大量数据时保持高效性能。

当向PriorityQueue添加元素时,它会根据元素的优先级进行排序。你可以通过实现Comparable接口或提供一个Comparator来定义元素的优先级。在取出元素时,PriorityQueue总是返回优先级最高(最小值)的元素。

需要注意的是,PriorityQueue不是线程安全的,如果需要在并发环境下使用,可以考虑使用PriorityBlockingQueue,这是一个线程安全的阻塞优先级队列。

4、LinkedList

LinkedList的实现原理是通过双向链表数据结构来实现的。它使用节点(Node)来存储数据,每个节点包含两部分:数据和指向下一个节点(以及指向前一个节点,如果是双向链表)的引用。

在LinkedList中,数据元素是存储在节点中的,节点之间通过引用相互连接,形成一个链式结构。这种结构使得LinkedList在插入和删除元素时,只需要改变相应节点的引用即可,而不需要像数组那样移动大量元素。

LinkedList的实现通常包括头节点(head)和尾节点(tail),这些节点用于快速访问列表的起点和终点。添加元素时,新节点被添加到尾部,并通过修改尾节点和新节点的引用来确保链表的完整性。删除元素时,通过找到要删除的节点并修改它的前一个节点和后一个节点的引用,将它从链表中移除。

LinkedList的实现可以支持在链表任意位置插入和删除元素,但需要注意的是,为了找到指定位置,可能需要从头节点开始遍历链表,这将花费线性时间复杂度。

总体而言,LinkedList的实现原理是通过链表数据结构来动态管理元素的存储和访问,它具有高效的插入和删除操作,并可根据需要动态增长或缩小。

5、ArrayDeque

ArrayDeque的实现原理是通过循环数组来实现的。它使用了一个数组来存储元素,并且配合两个索引指针head和tail来进行元素的添加和移除操作。

为了满足在数组两端都能插入和删除元素的需求,ArrayDeque采用了循环数组的设计。这意味着当指针到达数组的末端时,它会从数组的起始位置继续。因此,实际上head指针指向的是首端第一个有效元素,tail指针指向的是尾端第一个可以插入元素的空位。

这样的设计使得ArrayDeque在插入和删除元素时,不需要像普通数组那样移动大量元素来填补空间。它可以直接在头部或尾部进行元素的添加或移除操作,并且只需要调整head和tail指针的位置。

ArrayDeque的底层实现通过维护一个固定大小的数组和两个指针,实现了双端队列(Deque)的功能,并提供了高效的插入和删除操作。

总结来说,ArrayDeque的实现原理是利用循环数组的特性,通过head和tail两个指针来标记元素的插入和删除位置,从而实现了一个高效的双端队列。

六、Queue常见问题

空指针异常:当尝试从空(没有任何元素)的队列中获取或删除首个/最后一个元素时,会导致 NullPointerException 异常。请确保在执行这些操作之前先检查是否为空。

七、总结

队列是一种简单而常见的数据结构,它基于先进先出原则,并具有限制访问和动态大小等特点。适用于需要按照特定顺序管理和操作元素的场景,如任务调度、消息传递、广度优先搜索等。在选择是否使用队列时,请考虑其特点及限制,并确保正确处理可能发生的异常情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值