综述 - BlockingQueue 和 其它
一、概述
在开发过程中,经常会使用到一些数据结构,如散列表(HashMap)、队列(Queue)等,但是有一部分数据结构是线程不安全的,即在并发场景下,数据的获取和存储会出现问题。
为了解决这种问题,JDK提供了几个并发安全的数据结构,如下图所示:
由上图可知,JDK中提供的并发容器有:
ConcurrentHashMap
:分片加锁实现高性能散列表。ConcurrentLinkedQueue
:采用CAS算法实现的非阻塞队列。BlockingQueue
:使用锁对象实现的阻塞队列。
实现一个线程安全的队列有两种方式:
- 方式1:使用阻塞算法,使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。
- 方式2:使用非阻塞算法 (CAS)。非阻塞的实现方式是使用循环CAS的方式来实现。
ConcurrentHashMap
、ConcurrentLinkedQueue
将单独介绍。本文主要讲解几种阻塞队列(BlockingQueue
)的特点。
二、阻塞队列的基本操作
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个操作支持阻塞的插入和移除方法。
- 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满的状态。
- 支持阻塞的移除方法:当队列为空时,获取元素的线程会一致等待队列变为非空的状态。
阻塞队列的应用场景:
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是
从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
阻塞队列内提供了4中 API 处理方式,如下图所示:
- 抛出异常:
add(e)
:当队列满时,如果往队列里插入元素,会抛异常。remove()
:当队列空时,如果从队列里获取元素,会抛出异常。element()
:当队列空时,如果从队列里获取元素,会抛出异常。
- 返回特殊值:
offer(e)
:当队列满时,如果往队列里插入元素,会返回 false 表示插入失败。poll()
:当队列空时,如果从队列里获取元素,会返回 null。peek()
:当队列空时,如果从队列里获取元素,会返回 null。
- 一直阻塞:
put(e)
:当队列满时,如果往队列里插入元素,队列会阻塞当前线程,直到队列可用或者响应中断退出。take()
:当队列空时,如果从队列里获取元素,队列会阻塞当前线程,直到队列不为空。
- 超时退出:
offer(time, unit)
: 当队列满时,如果往队列里插入元素,队列会阻塞当前线程一段时间,如果超过了指定的时间,当前线程就会退出。poll(time, unit)
:当队列空时,如果从队列里获取元素,队列会阻塞当前线程一段时间,如果超过了指定的时间,当前线程就会退出。
三、几种常见的阻塞队列
JDK1.7 提供了7个阻塞队列:
ArrayBlockingQueue
:一个由数组结构组成的有界阻塞队列。LinkedBlockingQueue
:一个由链表结构组成的有界阻塞队列。PriorityBlockingQueue
:一个支持优先级排序的无界阻塞队列。DelayQueue
:一个使用优先级队列实现的无界阻塞队列。SynchronousQueue
:一个不存储元素的阻塞队列。LinkedTransferQueue
:一个由链表结构组成的无界阻塞队列。LinkedBlockingDeque
:一个由链表结构组成的双向阻塞队列。
1. ArrayBlockingQueue
- ArrayBlockingQueue 是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。
- 默认情况下使用非公平锁进行队列访问 (公平锁会降低吞吐量)。
2. LinkedBlockingQueue
- LinkedBlockingQueue 是一个用链表实现的有界阻塞队列。此队列按照先进先出的原则对元素进行排序。
- 此队列的默认和最大长度为Integer.MAX_VALUE。
3. PriorityBlockingQueue
- PriorityBlockingQueue 是一个支持优先级的无界阻塞队列。
- 默认情况下元素采取自然顺序升序排列。
- 支持自定义类实现compareTo()方法来指定元素排序规则。
- 同优先级元素,PriorityBlockingQueue不能保证其顺序。
4. DelayQueue
- DelayQueue 是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。
- 队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。
- 只有在延迟期满时才能从队列中提取元素。
DelayQueue 的应用场景:
- 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
- 定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行。
5. SynchronousQueue
- SynchronousQueue 是一个不存储元素的阻塞队列。
- 每一个put操作必须等待一个take操作,否则不能继续添加元素。
- 默认情况下线程采用非公平性策略访问队列。
优势: SynchronousQueue
的吞吐量高于 LinkedBlockingQueue
和 ArrayBlockingQueue
。
6. LinkedTransferQueue
LinkedTransferQueue 是一个由链表结构组成的无界阻塞TransferQueue队列。
7. LinkedBlockingDeque
- LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列。
双向队列:指可以从队列的两端插入和移出元素。- 双向阻塞队列可以运用在 工作窃取 模式中。
四、阻塞队列的实现原理
如果队列是空的,消费者会一直等待,当生产者添加元素时,消费者是如何知道当前队列
有元素的呢?
可以使用 Condition
接口的等待通知模式来实现。具体请参考:Lock(五) — LockSupport 和 Condition接口