队列,实质是一种存储数据的结构,通常用链表或者数组实现。一般具备FIFO(先进先出)的特性,当然也有双端队列(Deque),优先队列等。主要的操作:入队(Enqueue)和出队(Dequeue).
在Java中, 定义了队列的基本操作,接口类型为 java.util.Queue,接口定义如下所示。Queue 定义了两套队列操作方法:
public interface Queueextends Collection { //插入元素,成功返回true,失败抛出异常 boolean add(E e); //插入元素,成功返回true,失败返回false或抛出异常 boolean offer(E e); //取出并移除头部元素,空队列抛出异常 E remove(); //取出并移除头部元素,空队列返回null E poll(); //取出但不移除头部元素,空队列抛出异常 E element(); //取出但不移除头部元素,空队列返回null E peek(); }
Queue 作为先进先出队列,只能从头部取元素、插入元素到尾部。
Java 同样定义了双向队列 Deque,可以同时在头部、尾部插入和取出元素,接口定义如下所示,针对头部操作方法为 xxxFirst、针对尾部操作方法为 xxxLast:
public interface Dequeextends Queue { //插入元素到队列头部,失败抛出异常 void addFirst(E e); //插入元素到队列尾部,失败抛出异常 void addLast(E e); //插入元素到队列头部,失败返回false或抛出异常 boolean offerFirst(E e); //插入元素到队列尾部,失败返回false抛出异常 boolean offerLast(E e); //取出并移除头部元素,空队列抛出异常 E removeFirst(); //取出并移除尾部元素,空队列抛出异常 E removeLast(); //取出并移除头部元素,空队列返回null E pollFirst(); //取出并移除尾部元素,空队列返回null E pollLast(); //取出但不移除头部元素,空队列抛出异常 E getFirst(); //取出但不移除尾部元素,空队列抛出异常 E getLast(); //取出但不移除头部元素,空队列返回null E peekFirst(); //取出但不移除尾部元素,空队列返回null E peekLast(); //移除指定头部元素,若不存在队列不变,移除成功返回true boolean removeFirstOccurrence(Object o); //移除指定尾部元素,若不存在队列不变,移除成功返回true boolean removeLastOccurrence(Object o); //单向队列方法,参考Queue //栈方法,参考栈 //集合方法,参考集合定义 }
不仅如此,Java 并发工具包中定义了阻塞队列 BlockingQueue 和 BlockingDueue。阻塞队列在前面的队列定义基础上增加了以下几个方法,来支持阻塞操作:
take:取出并移除元素,如队列为空则一直阻塞直到有元素;
put:插入元素,如队列满则一直阻塞直到有空位可以插入元素;
可超时的offer:插入元素并指定超时时间,如队列满等待指定的时间;
可超时的poll:取出并移除元素,如队列空等待指定的时间。
一般情况下 offer() 和 poll() 方法配合使用,put() 和 take() 阻塞方法配合使用,add() 和 remove() 方法会配合使用,程序中常用的是 offer() 和 poll() 方法,因此这两个方法比较友好,不会报错。
Java 中的这些队列可以从不同的维度进行分类,例如可以从阻塞和非阻塞进行分类,也可以从有界和无界进行分类,而本文将从队列的功能上进行分类,例如:优先队列、普通队列、双端队列、延迟队列等
阻塞队列(Blocking Queue)提供了可阻塞的 put 和 take 方法,它们与可定时的 offer 和 poll 是等价的。如果队列满了 put 方法会被阻塞等到有空间可用再将元素插入;如果队列是空的,那么 take 方法也会阻塞,直到有元素可用。当队列永远不会被充满时,put 方法和 take 方法就永远不会阻塞。在java包"java.util.concurrent"中,提供六个实现了"BlockingQueue"接口的阻塞队列:
ArrayBlockingQueue 用数组实现的有界阻塞队列;
LinkedBlockingQueue 基于链表实现的有界阻塞队列
PriorityBlockingQueue是一个带优先级的队列,基于堆数据结构的;
DelayQueue是在PriorityQueue基础上实现的,底层也是数组构造方法,是一个存放Delayed 元素的无界阻塞队列;
SynchronousQueue 一个没有容量的队列 ,不会存储数据;
LinkedBlockingDeque 是双向链表实现的双向并发阻塞队列;
所有无Blocking Queue的都是非阻塞,并且它不会包含 put 和 take 方法。
是指有固定大小的队列,比如设定了固定大小的 ArrayBlockingQueue,又或者大小为 0 的 SynchronousQueue。
指的是没有设置固定大小的队列,但其实如果没有设置固定大小也是有默认值的,只不过默认值是 Integer.MAX_VALUE。
Deque是一个双端队列接口,继承自Queue接口,Deque的实现类是LinkedList、ArrayDeque、LinkedBlockingDeque,其中LinkedList是最常用的。
优先队列(PriorityQueue)是一种特殊的队列,它并不是先进先出的,而是优先级高的元素先出队。 优先队列是根据二叉堆实现的。最大堆和最小堆。
延迟队列(DelayQueue)是基于优先队列 PriorityQueue 实现的,它可以看作是一种以时间为度量单位的优先的队列,当入队的元素到达指定的延迟时间之后方可出队。
最典型的就是线程池,不同的线程池都是基于不同的队列来实现多任务等待的。
在项目的一些核心业务且生产和消费速度相似的场景中:订单完成的邮件/短信提醒。 订单系统中当用户下单成功后,将信息放入ArrayBlockingQueue中,由消息推送系统取出数据进行消息推送提示用户下单成功。如果订单的成交量非常大,那么使用ArrayBlockingQueue就会有一些问题,固定数组很容易被使用完,此时调用的线程会进入阻塞,那么可能无法及时将消息推送出去,所以使用LinkedBlockingQueue比较合适,但是要注意消费速度不能太低,不然很容易内存被使用完。
在项目上存在优先级的业务:VIP排队购票 用户购票的时候,根据用户不同的等级,优先放到队伍的前面,当存在票源的时候,根据优先级分配
由于是基于优先级队列实现,但是它比较的是时间,我们可以根据需要去倒叙或者正序排列(一般都是倒叙,用于倒计时)。所以适用于:
订单超时取消功能、网站刷题倒计时 用户下订单未支付开始倒计时,超时则释放订单中的资源,如果取消或者完成支付,我们再将队列中的数据移除掉。
参考线程池newCachedThreadPool()。 如果我们不确定每一个来自生产者请求数量但是需要很快的处理掉,那么配合SynchronousQueue为每个生产者请求分配一个消费线程是最简洁的办法。
cking Queue)提供了可阻塞的 put 和 take 方法,它们与可定时的 offer 和 poll 是等价的。如果队列满了 put 方法会被阻塞等到有空间可用再将元素插入;如果队列是空的,那么 take 方法也会阻塞,直到有元素可用。当队列永远不会被充满时,put 方法和 take 方法就永远不会阻塞。在java包"java.util.concurrent"中,提供六个实现了