Queue用于模拟"队列"
这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。
【1】Queue的几个接口
Queue是设计用于在处理前保存元素的集合。
除了基本的 java.util.Collection 操作外,队列还提供其他插入、提取和检查操作。这些方法都有两种形式:一种是在操作失败时抛出异常,另一种是返回特殊值(取决于操作,null或 false)。设计了插入操作的后一种形式专门用于容量受限的队列。在大多数实现中,插入操作不会失败也就是允许元素为null(但是不鼓励插入null)。
队列通常(但不一定)以FIFO(先进先出)方式对元素进行排序。例外情况包括优先级队列,该队列根据提供的比较器或元素的自然排序对元素排序,以及后进先出LIFO 队列(或堆栈),该队列对元素进行后进先出排序。
无论使用何种排序,队列的 头 都是通过调用 remove 或 poll移除的元素。在FIFO队列中,所有新元素都插入队列的尾部。其他类型的队列可以使用不同的放置规则。每个Queue实现都必须指定其排序属性。
Queue 接口没有定义在并发编程中常见的 阻塞队列方法。这些方法(等待元素出现或空间可用)在java.util.concurrent.BlockingQueue接口中定义,该接口扩展了Queue 接口。
① Queue([kjuː])
如下所示,Queue提供了关于元素增加、移除以及获取的方法。
public interface Queue<E> extends Collection<E> { //如果可以在不违反容量限制的情况下立即将指定元素插入此队列,则在成功时返回 true, //如果当前没有可用空间,则抛出 IllegalStateException。 boolean add(E e); //如果可以在不违反容量限制的情况下立即将指定的元素插入此队列中。 //当使用容量受限队列时,此方法通常优于{add},后者仅通过抛出异常表示插入失败 boolean offer(E e); //检测并移除队列的头结点,如果队列为空,抛出异常NoSuchElementException E remove(); // 检测并移除队列的头结点,如果队列为空,返回null E poll(); //获取队列的头结点,如果队列为空抛出异常NoSuchElementException E element(); //获取队列的头结点,如果队列为空返回null E peek(); }
常见实现有ConcurrentLinkedDeque。
② Deque
Deque继承自Queue接口,Deque接口代表一个"双端队列",双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用。
deque是“双端队列double ended queue”的缩写,通常发音为“deck”。
大多数Deque实现对它们可能包含的元素数量没有固定限制,但该接口支持容量受限的Deque以及没有固定大小限制的Deque。
该接口定义了访问deque两端元素的方法。提供了插入、移除和检查元素的方法。这些方法都有两种形式:一种是在操作失败时抛出异常,另一种是返回特殊值(取决于操作, null或 false)。后一种形式的插入操作专门设计用于容量受限的.Deque实现;在大多数实现中,插入操作不会失败。
下表总结了上述十二种方法:
此接口扩展了 Queue 接口。当deque用作队列时,会产生FIFO(先进先出First-In-First-Out)行为。元素添加在deque的末尾,并从开头移除。从 Queue 接口继承的方法完全等同于Deque 方法,如下表所示:
Queue Method | Equivalent Deque Method |
java.util.Queue#add add(e) | addLast(e) |
java.util.Queue#offer offer(e) | offerLast(e) |
java.util.Queue#remove remove() | removeFirst() |
java.util.Queue#poll poll() | pollFirst() |
java.util.Queue#element element() | getFirst() |
java.util.Queue#peek peek() | peekFirst() |
DEQUE还可以用作后进先出LIFO (Last-In-First-Out)堆栈。此接口应优先于遗留 Stack 类使用。
当deque用作堆栈时,元素从deque的开头被推送和弹出。堆栈方法完全等同于 Deque 方法,如下表所示:
Stack Method | Equivalent Deque Method |
push(e) | addFirst(e) |
pop() | removeFirst() |
peek() | peekFirst() |
请注意,当deque用作队列或堆栈时,peek 方法同样有效。在这两种情况下,元素都是从deque的开头检测的。
与 List 接口不同,此接口没有提供对元素索引访问的支持。也就是说你不能使用类似get(index)来获取目标位置的元素。
虽然 很多 Deque 的实现并不严格要求禁止插入空元素,但是不鼓励用户插入null元素。这是因为 null 被各种方法用作特殊返回值,以指示deque为空。
Deque的实现有LinkedList、ArrayDeque等。
③ BlockingQueue
其是java.util.concurrent(并发)包下的一个接口,BlockingQueue继承自Queue。它还支持在检索元素时等待队列变为非空,在存储元素时等待队列中空间变为可用的操作。
BlockingQueue}方法有四种形式,有不同的处理操作的方法,这些操作不能立即满足,但可能在将来的某个时候满足。
第一种抛出异常,第二种返回一个特殊值(取决于操作, null 或 false ),第三种无限期阻塞当前线程,直到操作成功,第四种是在给定时间前阻塞,之后就放弃。下表总结了这些方法:
Throws exception |
Special value | Blocks | Times out | |
Insert | add(e) | offer(e) | put(e) | offer(e, time, unit) |
Remove | remove() | poll() | take() | poll(time, unit) |
Examine | element() | peek() | not applicable | not applicable |
BlockingQueue不接受 null元素。实现类在 add、 put 或 offer null元素将会时抛出 NullPointerException。null 用作指示 poll 操作失败的敏感值。
BlockingQueue可能是容量受限的。在任何给定时间,它可能具有remainingCapacity(剩余容量),超过该值 put 操作就不能无阻塞地添加额外的元素。
没有任何内在容量约束的BlockingQueue始终报告 Integer.MAX_VALUE 的剩余容量。
BlockingQueue实现主要用于生产者-消费者队列,但还支持java.util.Collection接口。因此,例如,可以使用 remove(x)从队列中移除任意元素。但是,此类操作通常不会非常有效地执行,仅用于偶尔使用,例如取消排队消息时。
BlockingQueue 实现是线程安全的。所有排队方法都使用内部锁或其他形式的并发控制以原子方式实现其效果。但是,除非在实现中另有规定,否则Collection的批量操作addAll、 containsAll、 retainAll 和 removeAll 不一定是原子执行的。因此,例如,addAll©可能在只添加了集合.c 中的一些元素后失败(引发异常)。
BlockingQueue 本质上不支持任何类型的close or shutdown操作来标明不再添加元素。这些特性的需要和使用往往取决于实现。例如,一种常见的策略是生产商插入特殊的end-of-stream或poison对象,当消费者使用这些对象时,会相应地进行处理。
使用示例,基于典型的生产者-消费者场景。请注意, BlockingQueue 可以安全地用于多个生产者和多个消费者。
class Producer implements Runnable { private final BlockingQueue queue; Producer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { queue.put(produce()); } } catch (InterruptedException ex) { ... handle ...} } Object produce() { ... } } class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { consume(queue.take()); } } catch (InterruptedException ex) { ... handle ...} } void consume(Object x) { ... } } class Setup { void main() { BlockingQueue q = new SomeQueueImplementation(); Producer p = new Producer(q); Consumer c1 = new Consumer(q); Consumer c2 = new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }}
内存一致性影响:与其他并发集合一样,在将对象放入 BlockingQueue的线程中的操作happen-before在另一个线程的 对BlockingQueue中访问或移除该元素的操作之前。
常见实现类有SynchronousQueue、ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue等。
【3】 ArrayDeque
是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的、可重分配的Object[]
数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重新分配一个Object[]
数组来存储集合元素。
实例代码如下:
import java.util.*;public class ArrayDequeTest { public static void main(String[] args) { ArrayDeque stack = new ArrayDeque(); //依次将三个元素push入"栈" stack.push("Java"); stack.push("轻量级Java EE企业应用实战"); stack.push("Android"); //输出:[Java, 轻量级Java EE企业应用实战 , Android] System.out.println(stack); //访问第一个元素,但并不将其pop出"栈",输出:Android System.out.println(stack.peek()); //依然输出:[Java, 轻量级Java EE企业应用实战 , Android] System.out.println(stack); //pop出第一个元素,输出:Android System.out.println(stack.pop()); //输出:[Java, 轻量级Java EE企业应用实战] System.out.println(stack); } }