一、基本定义
阻塞队列的顶级接口是BlockingQueue,它实现了Queue这个接口,官方对它的解释是:一个队列,它还支持在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用的操作,典型的应用场景有生产者-消费者、线程池和消息中间件等。
BlockingQueue 为元素的操作有四种形式,用不同的方式处理不能立即满足,但可能在未来某个时间点满足的操作:
- 抛出异常,当阻塞队列满时,再向队列中add添加元素会抛出IllegalStateException:Queue full;当阻塞队列空时,再向队列remove移除元素会抛出NoSuchElementException。
- 插入方法,成功返回true,失败返回false移除方法,成功返回移出队列的元素,失败返回null。
- 当阻塞队列满时,生产者线程继续往队列put元素,队列会一直阻塞生产者线程直到put数据或响应中断输出;当阻塞队列空时,消费者线程从队列take元素,队列会一直阻塞消费者线程直至队列中出现元素。
- 当阻塞队列满时,队列会阻塞生产者线程一段时间,超过时限之后生产者线程自动退出 。
方法名如下表所示:
抛出异常 | 返回特殊值 | 无限期阻塞 | 超时阻塞 | |
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove | poll() | take() | poll(time,unit) |
检查 | element() | peek() |
同时,Java为不同的业务场景还提供了不同的BlockingQueue实现类,具体如下:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按照FIFO(先进先出)的原则对元素进行排序;
- LinkedBlockingQueue:一个基于链表结构的有界(大小默认值为Integer.MAX_VALUE)阻塞队列,此队列按照FIFO(先进先出)的原则对元素进行排序,吞吐量通常要高于ArrayBlockingQueue。
- SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另外一个线程调用移除操作,否则插入操作一直处于阻塞状态。
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
二、源码分析
以ArrayBlockingQueue为例,它是基于数组结构的有界阻塞队列,具有以下特性:
- 队列的头部是在队列中时间最长的元素,队列的尾部是在队列中时间最短的元素,新元素被插入到队列的尾部,队列检索操作获取队列头部的元素。
- 同时也是一个经典的“有界缓冲区”,其中一个固定大小的数组保存由生产者插入并由消费者提取的元素,一旦创建,容量将无法更改。
- 尝试将元素放入完整队列将导致操作阻塞;尝试从空队列中获取元素同样会阻塞。
- 支持对等待的生产者和消费者线程进行排序的可选公平策略。默认情况下,采取非公平策略,可以通过改变构造器参数fair的值为true来实例化公平阻塞队列,公平性设置为 true 的队列以 FIFO 顺序授予线程访问权限。公平性通常会降低吞吐量,但会降低可变性并避免饥饿。
2.1 构造器
ArrayBlockingQueue共包含3个构造器:
1. 指定底层数组容量,该方法中调用了两参构造器,默认创建为非公平阻塞队列,如果capacity小于1,会抛出IllegalArgumentException异常,
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
2. 指定底层数组数量以及是否采用公平策略,初始化put和take方法中需要用到的关键成员变量:同步锁lock、非空条件队列notEmpty以及未满条件队列notFull;如果capacity小于1,会抛出IllegalArgumentException异常,
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();
}
3. 指定底层数组数量、是否采用公平策略和初始元素集合,首先调用了两参构造器创建阻塞队列,然后上锁,按集合迭代器的遍历顺序向队列中添加元素。如果capacity小于c.size()或小于1,会抛出IllegalArgumentException异常,如果集合c或者集合c中的任意元素为空,抛出NullPointerException异常:
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
2.2 元素操作方法
1. add(E e):可以在不超过队列容量的情况下立即插入指定元素,则在此队列的尾部插入指定元素,成功时返回 true,如果此队列已满则抛出 IllegalStateException,实际底层通过调用offer方法来实现。
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
2. remove(Object o):循环遍历从队列中移除第一个等于o的元素,移除成功返回true,移除失败返回false
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
3. element():使用peek方法检索队列的队头,如果不为空则返回该元素,并不会改变队列结构,如果为空则抛出NoSuchElementException
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
4. offer(E e):首先对元素进行判空,然后可以在不超过队列容量的情况下立即插入,则在此队列的尾部插入指定元素,成功时返回 true,如果队列已满则返回 false。 相比 add方法更友善,后者只能通过抛出异常来表明无法插入元素。
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
5. poll():移除队首元素并返回
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
6. peek(): 检索队列头部元素并返回该元素,并不改变队列结构,如果队列为空则返回null
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
7. put(E e):在队列的尾部插入指定元素,如果队列已满,则等待队列空间可用,通过调用未满条件队列的await方法实现,等待被唤醒
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();
}
}
8. take():移除队首元素,如果队列为空,则等待队列中有元素,通过调用非空条件队列的await方法实现,等待被唤醒
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
9. offer(E e, long timeout, TimeUnit unit):在offer(e)的基础上加了一个超时等待时间,向队尾插入元素,若队列已满,则阻塞指定时间,若此期间队列可插入则插入元素返回true,超时则返回false
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
10. poll(long timeout, TimeUnit unit):在poll方法的基础上加了一个超时等待时间,移除队首元素,若队列为空,则阻塞指定时间,若此期间队列不为空则移除队首元素返回true,超时则返回false
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
11. enqueue(E x):元素入队列的关键方法,在线程持有锁时调用,向队尾插入元素,并唤醒非空条件队列notEmpty中等待的线程
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();
}
12. 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];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
13. contains(Object o):如果队列包含至少一个o元素,返回true,否则返回false
public boolean contains(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i]))
return true;
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}