一.简介
以下摘自jdk1.8源码:
A {@link java.util.Queue} that additionally supports operations
that wait for the queue to become non-empty when retrieving an
element, and wait for space to become available in the queue when
storing an element.
- 一个队列
- 这个队列要求在队列非空时取出元素
- 这个队列要求在队列非满时存储元素
总结:
阻塞队列是一个队列。试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
二.源码:
BlockingQueue是一个接口:
public interface BlockingQueue<E> extends Queue<E>
实现Queue接口,这是一个队列结构:先进先出。
BlockingQueue的实现类很多
常用的有ArrayBlockingQueue,LinkedBlockingQueue, SynchronousQueue,以下举例使用ArrayBlockingQueue。
ArrayBlockingQueue 分析
先看一下声明部分
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//和ArrayList一样,底层是用的数组
final Object[] items;
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
注意有两个Condition一个表示非空,一个表示非满,还有一个Lock
再看一下核心的方法
put:
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();
}
}
/** Condition for waiting puts */
private final Condition notFull;
可以看出put方法的逻辑也是通过Condition配合ReentrantLock实现的,当count和数组长度items.length相等,说明队列满,则调用Conditon的await;否则说明有空位,调用enqueue()方法:
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
前几行都是比较常见的添加元素的方法,注意最后要调用Condition的signal方法,唤醒在notEmpty这个condition上阻塞的线程
take:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
take的逻辑和put基本一样,当Count=0,当前condition(notEmpty)阻塞,否则返回dequeue(),最后通知生产者,唤醒生产者线程(在notFull处阻塞)
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; //取到E
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
/**
@key jdk1.8的新特性迭代器特性,这里是因为元素的出队列所以清理和这个元素相关联的迭代器
*/
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
三.阻塞队列的应用
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
- 为什么需要BlockingQueue 。好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了
- 在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
- 在生产者消费者模型中,生产数据和消费数据的速率不一致,如果生产数据速度快一些,消费(处理)不过来,就会导致数据丢失。这时候我们就可以应用上阻塞队列来解决这个问题。
四.API分类
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
方法类型 | status |
---|---|
抛出异常 | 当阻塞队列满时,再往队列中add会抛IllegalStateException: Queue full 当阻塞队列空时,在网队列里remove会抛 NoSuchElementException |
特殊值 | 插入方法,成功true失败false 移除方法,成功返回出队列的元素,队列里没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞线程知道put数据或响应中断退出 当阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费者线程知道队列可用。 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出 |