Java集合之Queue

来源:互联网 发布:七氟醚mac值换算 编辑:程序博客网 时间:2024/05/18 02:41

Queue

一般我们说的Queue是FIFO,此外还有指定排序方式的PriorityQueue以及LIFO(Stack),以下情况Queue默认都指FIFO.

Queue主要提供的方法就是入队和出队,出队时区分在获取队首元素时是否删除队首元素,此外在入队出队时区分失败时是否抛出异常。

操作 失败时抛出异常方法 失败时返回特殊值 插入队尾 add(E) offer(E) 获取并删除队首 remove() poll() 获取队首 element() peek()

成功时三种类型操作语义相同,如果我们容忍失败,比如队列为空时获取删除、队列大小固定时添加,应该使用offer/poll/peek方法。

Queue内部数组和链表

队列分单向队列和双向队列以及阻塞和非阻塞

单向AbstractQueue

AbstractQueue主要实现了Queue中的add/remove/element方法,本质上还是调用offer/poll/peek, 只是在失败是会抛出异常,而非返回false/null

public boolean add(E e) {    if (offer(e))        return true;    else        throw new IllegalStateException("Queue full");}public E remove() {    E x = poll();    if (x != null)        return x;    else        throw new NoSuchElementException();}public E element() {    E x = peek();    if (x != null)        return x;    else        throw new NoSuchElementException();}

单向Queue其实是双向Queue的特例,JDK中没有提供单向非阻塞Queue的实现,一般的非阻塞Queue都是双向Queue。

线程安全ConcurrentLinkedQueue

线程安全的基于链表实现的AbstractQueue,采用CAS实现线程安全

阻塞BlockingQueue

队列为空获取元素或队列已满添加元素会阻塞在那里,提供了put(e)/take()阻塞方法,以及带有超时时间的offer/poll方法。

BlockingQueue都是线程安全的。

ArrayBlockingQueue

基于循环数组实现,初始长度在构造函数中给出,队列的长度受限,用以保证生产者与消费者的速度不会相差太远。有一把公共的锁和notFull、notEmpty两个Condition管理队列满或空时的阻塞状态。

LinkedBlockingQueue

可指定初始长度,如不指定则为Integer.MAX_VALUE,利用链表的特征,分离了takeLock与putLock两把锁,用notEmpty、notFull两个condition管理队列满或空时的阻塞状态。

双向Deque

Deque(Double Ended Queue)继承自Queue,Deque的特性是在队尾和队首都可以添加删除获取,因此,除了实现Queue中的方法,它提供了语义更明确的方法,同样区分失败时是否抛出异常。

队首:

操作 失败时抛出异常方法 失败时返回特殊值 插入队尾 addFirst(E) offerFirst(E) 获取并删除队首 removeFirst() pollFirst() 获取队首 getFirst() peekFirst()

队尾:

操作 失败时抛出异常方法 失败时返回特殊值 插入队尾 addLast(E) offerLast(E) 获取并删除队首 removeLast() pollLast() 获取队首 getLast() peekLast()

ArrayDeque

使用循环数组实现,默认数组大小为16,当数组满时动态扩容至两倍。

LinkedList

List中介绍过,LinedList是双向链表,实现了Deque接口。

线程安全ConcurrentLinkedDeque

线程安全的基于链表实现的DeQueue,采用CAS实现线程安全

阻塞BlockingDeque

无ArrayBockingDeque

LinkedBlockingDeque

同LinkedBlockingQueue

PriorityQueue

PriorityQueue是用小顶堆来实现的,堆是二叉完全平衡树,使用数组来保存,Queue(n)的child是Queue(2n+1)和Queue(2n+2), Queue(n) <= Queue(2n+1) && Queue(n) <= Queue(2n+2), Queue(2n+1)与Queue(2n+2)大小关系不定,因此PriorityQueue中Queue(0)是堆顶且最小,出队的总是堆顶,因此优先级越低越先出队,并不是FIFO。如果希望优先级高的先出队就在实现Comprator时比较取负。

入队时放入数组末尾,然后跟parent比较,如果比parent小,就不断向上移动进行调整。

出队时选取Queue(0)后,就需要从左右child中选出最小的元素填充Queue(0), 虽然最小的元素一定是左右child之一,但直接填充会破坏完全二叉树结构,因此将最后一个元素Queue(size)放入Queue(0),然后再堆顶自上向下不断调整。

Queue(n)的左右child之间并无大小关系,因此,通过iterator遍历Queue并不有序。

由于内部是数组,在入队之前会检查队列是否满,如果已满会动态扩容为原来的1.5倍。

我们知道堆可以用来求解TopN问题,既然PriorityQueue内部是堆,也就可以用来求解TopN,只需将PriorityQueue内部大小限定为N,当有第N+1个元素入队时,就将队首元素出队,这样PriorityQueue内部保存的总是Priority最高的N个元素。我们开源的SQL on Storm解决方案Pike支持select top n内部解析就使用的是PriorityQueue实现的。

https://github.com/PPTV/Pike/blob/master/pike/src/main/java/com/pplive/pike/generator/trident/TopNAggregator.java

PriorityBlockingQueue

无界的PriorityQueue,内部结果同PriorityQueue,使用一把公共的锁实现线程安全。因为无界,空间不够时会自动扩容,所以入列时不会锁,出列为空时才会锁。

DelayQueue

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>    implements BlockingQueue<E> {    private transient final ReentrantLock lock = new ReentrantLock();    private final PriorityQueue<E> q = new PriorityQueue<E>();}public interface Delayed extends Comparable<Delayed> {    /**     * Returns the remaining delay associated with this object, in the     * given time unit.     *     * @param unit the time unit     * @return the remaining delay; zero or negative values indicate     * that the delay has already elapsed     */    long getDelay(TimeUnit unit);}

DelayQueue = BlockingQueue + PriorityQueue + Delayed,内部是PriorityQueue,
同样是无界的,出列时才会锁。一把公共的锁实现线程安全。元素需实现Delayed接口,每次调用时需返回当前离触发时间还有多久,小于0表示该触发了。pull()时会用peek()查看队头的元素,检查是否到达触发时间。Delay时间最短的在堆顶,当到达Delay时间后,堆顶元素优先出队。

DelayQueue一般用于保存有生命周期的对象,ScheduledThreadPoolExecutor用了类似的结构。

SynchronousQueue

长度为0,不能调用peek()方法来看队列中是否有数据元素,生产者线程对其的插入操作put必须等待消费者的移除操作take,反过来也一样。放入元素时,比如等待元素被另一条线程的消费者取走再返回。多用用线程间传递元素,JDK线程池里用它。

TransferQueue

TransferQueue继承了BlockingQueue并扩展了一些新方法。BlockingQueue(和Queue)是Java 5中加入的接口,它是指这样的一个队列:当生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素但队列为空时,消费者会被阻塞。

TransferQueue则更进一步,生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列里就完事)。新添加的transfer方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程transfer到另一个线程的过程中,它有效地实现了元素在线程之间的传递(以建立Java内存模型中的happens-before关系的方式)。

在队列中已有元素的情况下,调用transfer方法,可以确保队列中被传递元素之前的所有元素都能被处理。

LinkedTransferQueue实现了TransferQueue接口,内部使用CAS保持同步。