Java 集合系列之四:Queue基本操作
Posted haimishasha
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合系列之四:Queue基本操作相关的知识,希望对你有一定的参考价值。
1. Java Queue
1. Java Queue 重要观点
- Java Queue接口是Java Collections Framework的成员。
- Queue 实现通常不允许插入 null 元素
- 队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。无论使用哪种排序方式,队列的头 都是调用
remove()
或poll()
所移除的元素。在 FIFO 队列中,所有的新元素都插入队列的末尾。其他种类的队列可能使用不同的元素放置规则。每个 Queue 实现必须指定其顺序属性。 - 在处理元素前用于保存元素的 collection。除了基本的
Collection
操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)。 - Queue 接口并未定义阻塞队列的方法,而这在并发编程中是很常见的。
BlockingQueue
接口定义了那些等待元素出现或等待队列中有可用空间的方法,这些方法扩展了此接口。 -
抛出异常 返回特殊值
插入:add(e) offer(e) 插入一个元素
移除:remove() poll() 移除和返回队列的头
检查:element() peek() 返回但不移除队列的头。 - JDK中并发队列提供了两种实现,一种是高性能队列ConcurrentLinkedQueue,一种是阻塞队列BlockingQueue(7种阻塞队列),两种都继承自Queue。
- JDK中队列有两大类,一类是双端队列,一类是单端队列。
2. Java Queue类图
此接口是 Java Collections Framework 的成员。
Java Queue接口扩展了Collection接口。Collection接口 externs Iterable接口。
子接口:BlockingQueue, Deque, BlobkingDequeue
一些最常用的Queue实现类是LinkedList,ArrayBlickingQueue, LinkedBlockingQueue,PriorityQueue, PriorityBlockingQueue。
3. Java Queue 方法
boolean add(E e) //将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。 E element() //获取,但是不移除此队列的头。 boolean offer(E e) //将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。 E peek() //获取但不移除此队列的头;如果此队列为空,则返回 null。 E poll() //获取并移除此队列的头,如果此队列为空,则返回 null。 E remove() //获取并移除此队列的头。
2. ConcurrentLinkedQueue接口(JUC)
1. ConcurrentLinkedQueue 结构图
一个基于链接节点的无界线程安全队列。
2. ConcurrentLinkedQueue重要特点
-
一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。
-
此队列不允许使用 null 元素。
-
此实现采用了有效的“无等待 (wait-free)”算法,该算法基于 Maged M. Michael 和 Michael L. Scott 合著的 Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms 中描述的算法。
-
需要小心的是,与大多数 collection 不同,size 方法不是 一个固定时间操作。由于这些队列的异步特性,确定当前元素的数量需要遍历这些元素。
- 内存一致性效果:当存在其他并发 collection 时,将对象放入
ConcurrentLinkedQueue
之前的线程中的操作 happen-before 随后通过另一线程从ConcurrentLinkedQueue
访问或移除该元素的操作。
3. Deque接口
1. Deque结构图
一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。
队列:此接口扩展了 Queue
接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。
堆栈:双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack
类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,
4. LinkedList
1. LinkedList 结构图
LinkedList是基于链表实现的,从源码可以看出是一个双向链表。除了当做链表使用外,它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList不是线程安全的,继承AbstractSequentialList实现List、Deque、Cloneable、Serializable。
- LinkedList继承AbstractSequentialList,AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数。这些接口都是随机访问List的。
- LinkedList 实现 List 接口,能对它进行队列操作。
- LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
- LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
- LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
2. LinkedList 重要特点
- 本质实现:底层使用一个Node数据结构,有前后两个指针,双向链表实现。
- 线程安全:非同步的。
- 列表长度:LinkedList中元素个数用size记录。
- 列表容量:LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
- 内存:需要更多的内存,LinkedList 每个节点中需要多存储前后节点的信息,占用空间更多些(previous element next)。
- 元素允许为null。
- Fail-Fast机制:同ArrayList相同。
- 遍历方法:所有指定位置的操作都是从头开始遍历进行的。LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。源码中先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。Arrays.copyOf() 方法:
- 它适合删除,插入较多的场景。
5. ConcurrentLinkedDeque接口(JUC)
1. ConcurrentLinkedDeque 结构图
并发队列ConcurrentLinkedDeque,这是一个非阻塞,无锁,无界 ,线程安全双端操作的队列。简单说就是ConcurrentLinkedQueue的升级版,在JDK7之后才提供。该队列也不允许空元素,而且size方法并不是常量时间,其需要遍历链表,此时并发修改链表会造成统计size不正确。同样,bulk操作和equal以及toArray方法不保证原子性。
2. ConcurrentLinkedDeque重要特点
-
ConcurrentLinkedDeque 是双向链表结构的无界并发队列。从JDK 7开始加入到J.U.C的行列中。
-
使用CAS实现并发安全,与 ConcurrentLinkedQueue 的区别是该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除)。
-
适合“多生产,多消费”的场景。
-
内存一致性遵循对 ConcurrentLinkedDeque 的插入操作先行发生于(happen-before)访问或移除操作。
6. BlockingQueue接口(JUC)
1. BlockingQueue 结构图
JDK7提供了7个阻塞队列。分别是
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :一个由链表结构组成的可选有界阻塞队列。如果未指定容量,那么容量将等于
Integer.MAX_VALUE
。 2 31-1 - PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列,,只有在延迟期满时才能从中提取元素。
- SynchronousQueue:一个不存储元素、没有内部容量的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞TransferQueue队列。
- LinkedBlockingDeque:一个由链表结构组成的可选范围双向阻塞队列。如果未指定容量,那么容量将等于
Integer.MAX_VALUE
。 2 31-1
2. BlockingQueue重要特点
- 支持两个附加操作的
Queue
,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用(阻塞)。 - BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
-
BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
-
BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
-
BlockingQueue 实现主要用于生产者-使用者队列,BlockingQueue 可以安全地与多个生产者和多个使用者一起使用。但它另外还支持
Collection
接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。 -
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
-
BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
-
内存一致性效果:当存在其他并发 collection 时,将对象放入
BlockingQueue
之前的线程中的操作 happen-before 随后通过另一线程从BlockingQueue
中访问或移除该元素的操作。
7. ArrayBlockingQueue(JUC)
1. ArrayBlockingQueue 结构图
2. ArrayBlockingQueue 重要特点
-
一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。
-
这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
-
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
8. LinkedBlockingQueue(JUC)
1. LinkedBlockingQueue 结构图
一个由链接节点支持的可选有界队列。
2. LinkedBlockingQueue 重要特点
-
一个基于已链接节点的、范围任意的 blocking queue。此队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
-
可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。如果未指定容量,则它等于
Integer.MAX_VALUE
。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。
9. PriorityBlockingQueue(JUC)
1. PriorityBlockingQueue 结构图
一个基于优先级堆的无界优先级阻塞队列。
2. PriorityBlockingQueue 重要特点
-
一个基于优先级堆的无界阻塞队列。,它使用与类
PriorityQueue
相同的顺序规则,并且提供了阻塞获取操作。虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败(导致 OutOfMemoryError)。 -
此类不允许使用 null 元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象(这样做会导致抛出 ClassCastException)。
-
此类及其迭代器可以实现
Collection
和Iterator
接口的所有可选 方法。iterator()
方法中提供的迭代器并不 保证以特定的顺序遍历 PriorityBlockingQueue 的元素。如果需要有序地进行遍历,则应考虑使用 Arrays.sort(pq.toArray())。此外,可以使用方法 drainTo 按优先级顺序移除 全部或部分元素,并将它们放在另一个 collection 中。 -
在此类上进行的操作不保证具有同等优先级的元素的顺序。如果需要实施某一排序,那么可以定义自定义类或者比较器,比较器可使用修改键断开主优先级值之间的联系。例如,以下是应用先进先出 (first-in-first-out) 规则断开可比较元素之间联系的一个类。要使用该类,则需要插入一个新的 FIFOEntry(anEntry) 来替换普通的条目对象。
10. DelayQueue(JUC)
1. DelayQueue 结构图
一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
2. DelayQueue 重要特点
-
Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。
-
当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即使无法使用 take 或 poll 移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size 方法同时返回到期和未到期元素的计数。
-
此队列不允许使用 null 元素。
11. SychronousQueue(JUC)
1. SychronousQueue 结构图
一个不存储元素、没有内部容量的阻塞同步队列。
SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。
2. SychronousQueue 重要特点
-
一个不存储元素、没有内部容量的阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的头 是尝试添加到队列中的首个已排队插入线程的元素;如果没有这样的已排队线程,则没有可用于移除的元素并且 poll() 将会返回 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空 collection。
-
此队列不允许 null 元素。
-
同步队列类似于 CSP 和 Ada 中使用的 简单聚集(rendezvous)机制信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
-
对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。
- SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue。
12. LinkedTransferQueue(JUC)
1. LinkedTransferQueue 结构图
LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。
2. LinkedTransferQueue 重要特点
- 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。
13. LinkedBlockingDeque(JUC)
1. LinkedBlockingDeque 结构图
一个基于已链接节点的、任选范围的阻塞双端队列。
2. LinkedBlockingDeque 重要特点
- 一个基于已链接节点的、任选范围的阻塞双端队列。
- 可选的容量范围构造方法参数是一种防止过度膨胀的方式。如果未指定容量,那么容量将等于
Integer.MAX_VALUE
。只要插入元素不会使双端队列超出容量,每次插入后都将动态地创建链接节点。 - 大多数操作都以固定时间运行(不计阻塞消耗的时间)。异常包括
remove
、removeFirstOccurrence
、removeLastOccurrence
、contains
、iterator.remove()
以及批量操作,它们均以线性时间运行。
以上是关于Java 集合系列之四:Queue基本操作的主要内容,如果未能解决你的问题,请参考以下文章
Java基础知识回顾之四 ----- 集合ListMap和Set
java与es8实战之四:SpringBoot应用中操作es8(无安全检查)