这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战
前言
什么是阻塞队列?有那7种阻塞队列?为什么需要阻塞队列?通过剖析JDK 1.8 源码了解源码设计的精髓和巧妙的地方,并运用在我们实际的项目中,他山之石,可以攻玉。
1. 什么是阻塞队列
感觉并发编程上面的解释不是很容易理解,我们看下源码Doug Lea大佬的注释,意思是阻塞队列支持检索的时候可以等待队列变成非空(也就是阻塞等待), 存储元素的时候,等待空间可用。
BlockingQueue方法有四种形式,具有不同的处理操作的方法,这些操作不能立即得到满足,但可能在将来的某个时候得到满足:
- 抛出一个异常
- 返回一个特殊值(null 或 false,依赖具体的操作)
- 在操作可以成功前,无限期地阻塞当前线程
- 在放弃前只在给定的最大时间限制内阻塞。
2. JDK种7种阻塞队列
JDK 7提供了7个阻塞队列,如下。
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
2.1 ArrayBlockingQueue
ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原 则对元素进行排序。
看下ArrayBlockingQueue 依赖继承关系,底层是以数组
Object[] items;保存元素的。
2.2 LinkedBlockingQueue
LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
2.3 PriorityBlockingQueue
PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序 升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
2.4 DelayQueue
DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队 列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。 只有在延迟期满时才能从队列中提取元素。
使用场景:
- 缓存系统的设计
- 定时任务调度
2.5 SynchronousQueue
SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。
它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。使用以下构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列。
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
2.6 LinkedTransferQueue
LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻 塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。
2.6.1 transfer方法
2.6.2 tryTransfer方法
2.7 LinkedBlockingDeque
LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。
3. 阻塞队列的实现原理
我们初学线程池可能会有一个疑问,如果队列是空的,消费者会一直等待,当生产者添加元素时,消费者是如何知道当前队列有元素的呢? 如果是你你会怎么去设计?
使用通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。
以JDK ArrayBlockingQueue 源码分析
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
// 这个是阻塞队列的关键点所在, 定义了两个条件队列
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
我们看下put操作,源码不复杂,我简单解释一下while条件,判断如果队列满了, notFull.await() 等待,当队列不满了,继续执行入队操作。
我们可能比较疑惑为什么用while不是用if判断呢,因为多线程的情况下,其他线程先入队 条件队列的await和single,可以参考我之间的 Java并发编程-AQS源码之条件队列
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
take方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞等待在条件notEmpty上
while (count == 0)
notEmpty.await();
// 出队列
return dequeue();
} finally {
lock.unlock();
}
}
参考
《Java并发编程艺术》