一 概述
简介
从本质上说,BlockingQueue(阻塞队列)接口(下文简称阻塞队列)应该属于Collection(集)框架的范畴,因为其继承了Queue(队列)接口。但由于其大多数情况都被用于容纳/管理执行器中任务,因此通常都被划分到Executor(执行器)框架的范畴中。为了契合Executor(执行器)框架的运行使用,阻塞队列在Queue(队列)接口的基础上增加了许多新特性/定义,该知识点的详细内容会在下文详述。
阻塞队列最大的特点是阻塞(这应该一眼就能看出来…),换句话说,其定义上是线程安全的,即其子类无论采用了何种实现方式,都必须保证线程安全。该定义意味着阻塞队列实现类的绝大多数方法实现都会是原子性的(否则无法保证线程安全),但对于批量方法就并非如此。例如addAll()、containsAll()及removeAll()等方法完全可以在底层循环调用add()、contains()及remove()方法来达到目的,因此在整体执行中可能伴随着并发。总的来说,即只要求单方法原子性执行,而批量方法除非特别指定,否则没有原子性执行的要求。因此,对于批量方法部分成功部分失败的情况是可以被接受的。例如在addAll()方法执行的过程中有元素抛出异常而导致后续元素添加失败,但前期元素添加成功的场景。
阻塞队列不允许存null值。接口规定一旦尝试在阻塞队列中存null值时必须抛出空异常,这是较为少见的,因为一般来说接口都不会对null值做强制的定义,而是交给子类自身去决定。阻塞队列之所以强制不允许存null值是因为null已经被用来作为标志位使用,具体原因与“方法的不同形式”有关,该知识点的详细内容会在下文详述。
阻塞队列可能存在容量限定,这种阻塞队列被称为有界队列(与之相对的是无界队列,即容量理论上只受限于堆内存大小。当然,在实际实现中其大多都受到int类型的物理限制)。有界队列的容量限定与常规的容量限定不同。在Collection(集)框架中,虽然也存在容量限定的说法,例如在创建ArrayList(数组列表)类对象时可以通过相应的构造方法来指定容量,但那实际上只是初始容量罢了,当存储的元素到达上限时无一例外都会触发扩容。但有界队列没有扩容的说法,是真正意义上的容量限定。一旦元素数量达到上限,则无法将向内保存元素,除非有元素因为被移除/拿取而空出了容量。
方法的不同形式
所谓方法的不同形式,是指方法在保证自身核心操作不变的情况下实现了多种不同的回应形式来应对不同场景下的使用要求。例如对于插入,当容量不足时,有些场景希望在失败时抛出异常;而有些场景则希望能直接返回失败的标记值;而有些场景又希望可以等待直至有可用空间后成功新增为止…正因如此,阻塞队列特意提供了四种不同的形式风格以满足不同场景下的使用需求,因此一个方法最多(并非所有方法都实现了四种形式)可能有四种不同回应形式。具体四种回应形式如下:
- 异常:当不满足操作条件时直接抛出异常。该形式实质上是Collection(集)接口定义的,阻塞队列只是继承了该形式;
- 特殊值:当不满足操作条件时直接返回失败标记值。该形式实质上是Queue(队列)接口定义的,阻塞队列只是继承了该形式;
- 阻塞(无限等待):当不满足操作条件时无限等待,直至满足操作条件后执行;
- 超时(有限等待):当不满足操作条件时有限等待,如果在指定等待时间之前直至满足操作条件则执行;否则返回失败标记值。
二 方法
插入/放置
插入/放置是阻塞队列两大最核心也是最常用的方法之一,用于向阻塞队列的头部插入指定元素。由于场景的多样性需求,阻塞队列提供了该类方法四种形式的实现以供使用。
-
boolean add(E e) —— 新增 —— 向当前阻塞队列的头部插入指定元素。该方法是插入/放置方法“异常”形式的实现,当阻塞队列存在剩余容量时插入/放置成功并返回true;否则抛出非法状态异常。
-
boolean offer(E e) —— 提供 —— 向当前阻塞队列的头部插入指定元素。该方法是插入/放置方法“特殊值”形式的实现,当阻塞队列存在剩余容量时插入/放置成功并返回true;否则返回false。
-
void put(E e) throws InterruptedException —— 放置 —— 向当前阻塞队列的头部插入指定元素。该方法是插入/放置操作“阻塞”形式的实现,当阻塞队列存在剩余容量时插入/放置成功;否则等待至存在剩余容量为止。
-
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException —— 提供 —— 向当前阻塞队列的头部插入指定元素。该方法是插入/放置操作“超时”形式的实现,当阻塞队列存在剩余容量时插入/放置成功并返回true;否则在指定等待时间内等待至存在剩余容量,超出指定等待时间则返回false。
移除/拿取
移除/拿取是阻塞队列两大最核心也是最常用的方法之一,用于从阻塞队列的头部移除并获取元素。移除/拿取方法同样有四种形式的实现。
-
E remove() —— 移除 —— 从当前阻塞队列的头部移除并获取元素。该方法是移除/拿取方法中“异常”形式的实现,当阻塞队列存在元素时移除并返回头元素;否则抛出无元素异常。
-
E poll() —— 轮询 —— 从当前阻塞队列的头部移除并获取元素。该方法是移除/拿取方法中“特殊值”形式的实现,当阻塞队列存在元素时移除并返回头元素;否则返回null。
-
E take() throws InterruptedException —— 拿取 —— 从当前阻塞队列的头部移除并获取元素。该方法是移除/拿取方法中“阻塞”形式的实现,当阻塞队列存在元素时移除并返回头元素;否则等待至存在元素为止。
-
E poll(long timeout, TimeUnit unit) throws InterruptedException —— 轮询 —— 从当前阻塞队列的头部移除并获取元素。该方法是移除/拿取方法中“超时”形式的实现,当阻塞队列存在元素时移除并返回头元素;否则在指定等待时间内等待至存在元素,超出指定等待时间则返回null。
检查
检查也是阻塞队列的常用方法之一,用于从阻塞队列的头部获取元素,但并不会将元素从阻塞队列中移除,属于移除/拿取方法的阉割版。检查方法同样具备多形式的实现,但只有“异常”与“特殊值”两种。
-
E element() —— 元素 —— 从当前阻塞队列的头部获取元素。该方法是检查方法中“异常”形式的实现,当阻塞队列存在元素时返回头元素;否则抛出无元素异常。
-
E peek() —— 窥视 —— 从当前阻塞队列的头部获取元素。该方法是检查方法中“特殊值”形式的实现,当阻塞队列存在元素时返回头元素;否则返回null。
迁移
迁移也是阻塞队列的常用方法之一,用于将阻塞队列中的所有元素迁移至指定集中,并返回迁移的元素总数。迁移方法虽有两种实现,但并没有采用形式的设定。
-
int drainTo(Collection<? super E> c) —— 流失 —— 将当前阻塞队列中的所有元素迁移至指定集中,并返回迁移的元素总数。该方法不允许将队列自身作为参数传入,否则会抛出非法参数异常。
-
int drainTo(Collection<? super E> c, int maxElements) —— 流失 —— 将当前阻塞队列中的最多指定数量的元素迁移至指定集中,并返回迁移的元素总数。该方法不允许将队列自身作为参数传入,否则会抛出非法参数异常。
其它
- int remainingCapacity() —— 剩余容量 —— 获取当前阻塞队列的剩余容量。如果阻塞队列是无界队列,则永远返回Integer.MAX_VALUE。