简介
阻塞队列支持两个特性:1.当队列为空时,阻塞获取队列中元素的线程;2.当队列已满时,阻塞插入队列中元素的线程。J.U.C
中提供的高效且线程安全的队列,可以帮助我们更快速地编写多线程程序。
核心元素
- ArrayBlockingQueue:队列为定长数组,生产消费共用一把锁(默认非公平);
- LinkedBlockingQueue:队列为链表,采用独立锁;
- DelayQueue:无大小限制,插入不会阻塞,
- PriorityBlockingQueue:非公平锁
- SynchronousQueue:无缓冲的阻塞队列
队列的原理
ArrayBlockingQueue
- 基于数组实现,队列中的元素遵循 FIFO,一旦创建不可改变;
- 数组头存放在队列中停留时间最长的元素,获取元素会从头部获取;反之亦然;
- 生产消费公用一把锁(默认非公平),两个条件,notEmpty,notFull;
LinkedBlockingQueue
- 基于链表实现,队列中的元素遵循FIFO,可选容量(默认
Integer.MAX_VALUE
); - 两把锁,两个条件;takeLock-notEmpty;putLock-notFull;
PriorityBlockingQueue
- 组合 PriorityQueue(仅用于序列化),不接受null元素和不能够比较的元素;
如果不使用自定义的比较器,则队列中的元素必须实现Comparable
接口; - 底层物理结构采用数组数据结构,逻辑结构采用平衡二叉堆(小根堆,堆排序),初始化的容量大小为11,最大上限接受 Integer.MAX_VALUE-8,超大容量时可能会造成 OutOfMemoryError;
- 当队列中的元素处于同一优先级时,此队列中的元素操作并不能保证这些元素的访问顺序;如果需要一个严格的顺序,可以自己定义一个第二排序关键字(demo参见jdk文档);
- 一把锁(非公平,不可选),一个条件,lock-not Empty;
DelayQueue<E extends Delayed>
- 队列中的元素必须实现 Delayed 接口,只有到期元素(到期仅在元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回值小于等于零时发生)才能取出;但是队列的
size
方法会计算到期和未到期的元素总数;此队列中不允许插入null元素; - 组合 PriorityQueue,所有队列操作基于 PriorityQueue;
- leader,主从模式,用于优化阻塞通知的线程元素leader
- 一把锁(非公平,不可选),一个条件,lock-available;
使用技巧
- 阻塞队列不接受null的元素,add, put or offer 一个null值会抛出NullPointerException;
- 阻塞队列可能有容量限制,阻塞队列没有容量限制的默认容量为 Integer.MAX_VALUE(231-1)
- 阻塞队列主要被设计用于生产者和消费者的队列,但却实现了集合的接口;因此一些操作并不是很高效,比如 remove 方法,但却故意保留下来,用于个边场景,比如队列中的消息被取消了;
- 阻塞队列是线程安全的,所有的队列操作都是通过内部锁(ReentrantLock)或其他形式的并发手段实现的原子操作。但是批量的集合操作,并不一定是原子操作,例如 addAll, containsAll , retainAll and removeAll;
- 阻塞队列并不支持 close 或 shutdown 的操作来表明没有元素将会被添加,这种需求应该独立实现,比如通用的策略是生产者插入一个特殊对象,消费者获取到这个对象后,理解为队列中不会再有元素。
- 内存一致性原则:插入元素a的操作先行发生( happen-before)于访问或移除元素a的操作
应用场景
- ArrayBlockingQueue, LinkedBlockingQueue - 生产消费者模式问题,队列元素FIFO;
- PriorityBlockingQueue - 自定义优先级排列
- DelayQueue - 缓存系统设计,任务超时处理,替代定时任务扫描的业务场景