常用的这几个阻塞队列,都逐一解析了一遍。
今天做一个大概的总结。
.
- 线程安全
阻塞队列首先在强调一点,它是线程安全的,也就是支持多并发的。
需要说的要,线程安全,指的是入队出队等等操作,内部用了锁、或者其它的并发控制技术,
比如CAS,再比如延时队列中的 leader 设计。
但是,阻塞队列里提供的集合操作,不是线程安全的,
比如 containsAll
removeAll
等等,这些方法想要线程安全,需要自己实现。
.
- 阻塞
所谓阻塞,大概的意思是:
当队满的情况下,要放入元素,它可以一直等到,队列中有空位时,再让元素放进去。
当队空的情况下,要取出元素,它可以一直等到,队列中有新元素时,再让其取走。
也不是所有的入队/出队 操作都会阻塞,
比如插入元素
add( e)
方法,当没有空位时,会抛出异常。
offer(e)
方法,当没有空位时,会返回一个特殊值,比如 一个 boolean 值
put(e)
方法,当没有空位时,就会阻塞
offer(e, time, unit)
方法,当一定时限内没有空位,则会超时
BlockingQueue 中的 API 方法是很丰富的,可根据自己的需要选择不同的方法。
.
- 不接受 null 值
你不能把 null 插入到 阻塞队列中,会直接抛出 NullPointerException()
。
这是个前提要求,为啥呢?
因为有些方法,可能返回 null,它是有操作意义的。
比如 poll()
方法,可能会返回 null ,表明队列中没有元素。
.
- ArrayBlockingQueue
基于数组实现的,初始化时,就确定了数组的大小,也不支持扩容。入队和出队是同一把锁。
也就是说,同一时刻,只能进行其中的一种操作,不可能出队入队同一时刻发生。
内部维护了两个指针,分别是出队的下标,入队的下标。
需要注意一点的是,虽然是一把锁,但 Condition 却是两个 。
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
- LinkedBlockingQueue
基于链表实现,可近似认为容量没限制。
容量没有限制,如果一直有入队操作,会不会内存溢出呀?
采用的是双锁,出队操作对应出队锁,入队操作对应入队锁。
也就是说,同一时刻,出队与入队可并行。
.
- PriorityBlockingQueue
是特殊的一个阻塞队列,底层用了 PriorityQueue,也就是用了 堆
在入队也出队时,高效的实现元素排序,操作的时间复杂度为 O(logn)。
只要对 堆
它的默认初始容量是 11 ,能自动扩容。
元素少于 64 时,是一次扩容一倍,否则一次扩容 50%。
.
- SynchronousQueue
这是个更为特殊的阻塞队列,容量为0。
入队操作和出队操作,同时出现时,才会成功。
即 A 线程的入队操作,碰上 B 线程的出队操作,两者相当于现场传递。
单独的入队,单独的出队,要么是失败,要么是阻塞。
这个阻塞队列的代码实现,是很不好懂,它在 CachedThreadPool 中有被使用。
.
- DelayQueue
这是一个延时队列,底层用了PriorityQueue 实现排序功能,
实现了 Delayed 这个接口,可以做到到期的对象才能出队。
入队则不受什么限制。
在出队源码中,并发控制的非常好,而且还有一个leader 设计,很是独特。