1、Queue编程
Queue是队列接口是 Collection的子接口。除了基本的 Collection操做外,队列还提供其余的插入、提取和检查操做。每一个方法都存在两种形式:一种抛出异常(操做失败时),另外一种返回一个特殊值(null 或 false,具体取决于操做)。插入操做的后一种形式是用于专门为有容量限制的 Queue 实现设计的;在大多数实现中,插入操做不会失败。缓存
抛出异常 | 返回特殊值 | |
插入 | add(e) | offer(e) |
移除 | remove(e) | poll(e) |
检查 | element() | peek() |
队列一般(但并不是必定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的天然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。不管使用哪一种排序方式,队列的头 都是调用 remove() 或 poll() 所移除的元素。在 FIFO 队列中,全部的新元素都插入队列的末尾。其余种类的队列可能使用不一样的元素放置规则。每一个 Queue 实现必须指定其顺序属性。 安全
若是可能,offer 方法可插入一个元素,失败则返回 false。这与 Collection.add 方法不一样,该方法只能经过抛出未经检查的异常使添加元素失败。offer 方法设计用于正常的失败状况,而不是出现异常的状况,例如在容量固定(有界)的队列中。 并发
remove() 和 poll() 方法可移除和返回队列的头。到底从队列中移除哪一个元素是队列排序策略的功能,而该策略在各类实现中是不一样的。remove() 和 poll() 方法仅在队列为空时其行为有所不一样:remove() 方法抛出一个异常,而 poll() 方法则返回 null。 学习
element() 和 peek() 获取但不移除队列的头,element与 peek 惟一的不一样在于:此队列为空时将抛出一个异常。this
Queue 接口并未定义阻塞队列的方法,而这在并发编程中是很常见的。BlockingQueue 接口则定义了那些等待元素出现或等待队列中有可用空间的方法,这些方法扩展了此接口。 spa
Queue 实现一般不容许插入 null 元素,尽管某些实现(如 LinkedList)并不由止插入 null。即便在容许 null 的实现中,也不该该将 null 插入到 Queue 中,由于 null 也用做 poll 方法的一个特殊返回值,代表队列不包含元素。 线程
Queue 实现一般未定义 equals 和 hashCode 方法的基于元素的版本,而是从 Object 类继承了基于身份的版本,由于对于具备相同元素但有不一样排序属性的队列而言,基于元素的相等性并不是老是定义良好的。 设计
Queue 做为队列能够实现一个按固定顺序访问其内部元素的结构,与 LinkedList等实现不一样,Queue并不能获取指定位置的元素。code
在 ThreadPoolExecutor类中建立线程池时使用的是 BlockingQueue。BlockingQueue是 Queue的子接口,BlockingQueue的实现类有不少:ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue。
Deque与 Queue不一样在于,Deque是一个双端队列,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,一般读为“deck”。大多数 Deque 实现对于它们可以包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。 Deque不是咱们要学习的重点,下面就不提了。
咱们要用到的实现为 ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue,DelayedWorkQueue.其中 DelayedWorkQueue是 ScheduledThreadPoolExecutor的内部类实现。
顶层接口为 Queue,而后是 Queue的抽象实现类 AbstractQueue和子接口 BlockingQueue。图中四个类均继承于 AbstractQueue并实现 BlockingQueue接口,DelayedWorkQueue一样实现了 BlockingQueue,但DelayedWorkQueue继承自 AbstractCollection。
如下是Queue的源代码:
2、AbstractQueue
AbstractQueue提供某些 Queue 操做的主要实现。此类中的实现适用于基本实现不 容许包含 null 元素时。add、remove 和 element 方法分别基于 offer、poll 和 peek 方法,可是它们经过抛出异常而不是返回 false 或 null 来指示失败。
扩展此类的 Queue 实现至少必须定义一个不容许插入 null 元素的 Queue.offer(E) 方法,该方法以及 Queue.peek()、Queue.poll()、Collection.size() 和 Collection.iterator() 都支持 Iterator.remove() 方法。一般还要重写其余方法。若是没法知足这些要求,那么能够转而考虑为 AbstractCollection 建立子类。
如下是 AbstractQueue的源代码:
3、BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操做的队列。这两个附加的操做是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用,从而产生阻塞。阻塞队列经常使用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的缓存容器,而消费者也只从容器里拿元素。
BlockingQueue 的方法以四种形式出现,这四种形式的处理方式不一样:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操做),第三种是在操做能够成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
抛出异常 | 返回特殊值 | 阻塞 | 超时 | |
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | - | - |
• 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException("Queue full")异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
• 返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,若是没有则返回null
• 阻塞:当阻塞队列满时,若是生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
• 超时:当阻塞队列满时,队列会阻塞生产者线程一段时间,若是超过必定的时间,生产者线程就会退出。
BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用做指示 poll 操做失败的警惕值。
BlockingQueue 能够是限定容量的。它在任意给定时间均可以有一个 remainingCapacity,超出此容量,便没法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 老是报告 Integer.MAX_VALUE 的剩余容量。
BlockingQueue 实现主要用于生产者-消费者队列,但它另外还支持 Collection 接口。所以,举例来讲,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操做一般不 会有效执行,只能有计划地偶尔使用,好比在取消排队信息时。
题外话:所谓生产者消费者模式,这里简单介绍一下。好比咱们在餐厅吃饭,咱们就是消费者,餐厅的厨师就是生产者,而餐厅的服务员就是一个缓冲环节。当生产者制做好菜品(生产产品),交由服务员(缓冲区),由服务员将菜品送至顾客(消费者)品用。
生产者-消费者模式最重要的做用就是解耦,利用缓冲区将二者分离。若是每个厨师作完了菜都须要亲自送到顾客桌上,那么这样就是将厨师与顾客绑定到了一块儿。加入中间缓冲环节,也就是服务员,将送菜的任务交由服务员(缓冲区)去处理,这样生产者与消费者就能够各自作本身的事情了。在此模式下二者间支持并发操做,由于饭店的厨师确定不止一个,顾客也是如此。再有就是支持二者间不一样步,由于二者间的数量与效率是不一样步的,这就会致使生产与消费的速度不一样。
BlockingQueue 实现是线程安全的。全部排队方法均可以使用内部锁或其余形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操做(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。所以,举例来讲,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操做来指示再也不添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种经常使用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
注意,BlockingQueue 能够安全地与多个生产者和多个使用者一块儿使用。
如下是基于典型的生产者-消费者场景的一个用例:
当生产者与消费者线程启动后,首先生产者会不断往队列中添加产品,一旦队列填满则生产中止,而后消费者从队列中取出产品使用,显然过程当中使用了相似于 wait与 notify的流程,后面会详细分析。
如下是 BlockingQueue的源代码:
后续几篇介绍阻塞队列的相关实现。