队列
队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),就像现实中的一群人在排队买票,前面的人买好了离开,后面的人就要全部向前一步,补上空位。
Java中的Queue
Java中的Queue是一个接口,这个接口与List、Set同一级别,都是继承了Collection接口。LinkedList是双向链表,它实现了Dequeue接口。
Queue不仅仅是一个FIFO,还有同步操作在里面,就是生产者-消费者的同步问题的一个解决方案。
因此队列分为阻塞队列和非阻塞队列,而我们平时使用的一般是非阻塞队列,比如PriorityQueue、LinkedList等。
源码太过于晦涩,这里不作讲解。
阻塞队列
Java1.5以后java.util.concurrent包中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
五个阻塞队列
- ArrayBlockingQueue :一个基于数组的有界队列,在创建对象的时候必须制定容量大小。并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,所以一般来说默认为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
- LinkedBlockingQueue :一个基于链表的有界队列,原本我以为它是无界的,后来了解才知道它是一个“有界”的堵塞队列,只是它的capacity默认是Integer.MAX_VALUE。
- PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列,以上2种队列都是先进先出队列,而它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError。
- DelayQueue :基于PriorityQueue,延时阻塞无界队列。只有在延迟时间到了的时候才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法(消费者)返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。因此只有消费者会被阻塞。
- SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。
非阻塞队列
LinkedList: 实现了java.util.Queue接口和java.util.AbstractQueue接口
而PriorityQueue 和 ConcurrentLinkedQueue分别内置在这两个包中
- PriorityQueue 类是一个有序列表。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator 实现来定位。
- ConcurrentLinkedQueue 是基于链表的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以不需要知道队列的大 小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
阻塞队列 VS 非阻塞队列
特点概述
阻塞队列的特点是:当队列空时,获取元素的操作将会被阻塞,或者当队列满时,添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列。
使用非阻塞队列:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。
主要方法
非阻塞队列
- add(E e) : 将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;
- remove() :移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;
- offer(E e) :将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;
- poll() :移除并获取队首元素,若成功,则返回队首元素;否则返回null;
- peek() :获取队首元素,若成功,则返回队首元素;否则返回null。
阻塞队列
非阻塞队列的五种方法阻塞队列都包括,但是加上了同步措施。此外还有
- put(E e) : 用来向队尾存入元素,如果队列满,则等待;
- take() : 用来从队首取元素,如果队列为空,则等待;
- offer(E e,long timeout, TimeUnit unit) : 用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
- poll(long timeout, TimeUnit unit) : 用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;