1. 简介
ArrayBlockingQueue,一个由数组实现的有界阻塞队列。该队列采用 FIFO 的原则对元素进行排序添加的。
ArrayBlockingQueue 为有界且固定,其大小在构造时由构造函数来决定,确认之后就不能再改变了。
ArrayBlockingQueue 支持对等待的生产者线程和使用者线程进行排序的可选公平策略,但是在默认情况下不保证线程公平的访问,在构造时可以选择公平策略(fair = true
)。公平性通常会降低吞吐量,但是减少了可变性和避免了“不平衡性”。
2. 构造方法
先看看 java.util.concurrent.ArrayBlockingQueue
的构造方法,代码如下:
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
private static final long serialVersionUID = -817911632652898426L;
final Object[] items;
int takeIndex;
int putIndex;
int count;
// 重入锁
final ReentrantLock lock;
// notEmpty condition
private final Condition notEmpty;
// notFull condition
private final Condition notFull;
transient ArrayBlockingQueue.Itrs itrs;
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
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. 入队
ArrayBlockingQueue 提供了诸多方法,可以将元素加入队列尾部。
#add(E e)
方法:将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则抛出 IllegalStateException 异常。
#offer(E e)
方法:将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true ,如果此队列已满,则返回 false 。
#offer(E e, long timeout, TimeUnit unit)
方法:将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
#put(E e)
方法:将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。
3.1 add
// ArrayBlockingQueue.java
@Override
public boolean add(E e) {
return super.add(e);
}
// AbstractQueue.java
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
|
#add(E e)
方法,调用 #offer(E e)
方法,如果返回false,则直接抛出 IllegalStateException 异常。
3.2 offer
@Override
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();
}
}
|
- 首先,检查是否为
null
。
- 然后,获取 Lock 锁。获取锁成功后,如果队列已满则,直接返回 false 。
- 最后,调用
#enqueue(E e)
方法,它为入列的核心方法,所有入列的方法最终都将调用该方法,在队列尾部插入元素。
3.2.1 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;
// 总数+1
count++;
// 通知阻塞在出列的线程
notEmpty.signal();
}
|
- 该方法就是在
putIndex
(对尾)位置处,添加元素,最后调用 notEmpty
的 #signal()
方法,通知阻塞在出列的线程(如果队列为空,则进行出列操作是会阻塞)。
3.3 可超时的 offer
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 {
// <1> 若队列已满,循环等待被通知,再次检查队列是否非空
while (count == items.length) {
// 可等待的时间小于等于零,直接返回失败
if (nanos <= 0)
return false;
// 等待,直到超时
nanos = notFull.awaitNanos(nanos); // 返回的为剩余可等待时间,相当于每次等待,都会扣除相应已经等待的时间。
}
// 入队
enqueue(e);
return true;
} finally {
// 解锁
lock.unlock();
}
}
|
- 相比
#offer(E e)
方法,增加了 <1>
处:
- 若队列已满,调用
notFull
的 #awaitNanos(long nanos)
方法,等待被通知(元素出列时,会调用 notFull
的 #signal()
方法,进行通知阻塞等待的入列线程)或者超时。
- 被通知后,再次检查队列是否非空。若非空,继续向下执行,否则继续等待被通知。
3.4 put
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 获得锁
lock.lockInterruptibly();
try {
// <1> 若队列已满,循环等待被通知,再次检查队列是否非空
while (count == items.length)
notFull.await();
// 入队
enqueue(e);
} finally {
// 解锁
lock.unlock();
}
}
|
- 相比
#offer(E e)
方法,增加了 <1>
处:
- 若队列已满,调用
notFull
的 #await()
方法,等待被通知(元素出列时,会调用 notFull
的 #await()
方法,进行通知阻塞等待的入列线程)。
- 被通知后,再次检查队列是否非空。若非空,继续向下执行,否则继续等待被通知。
4. 出队
ArrayBlockingQueue 提供的出队方法如下:
#poll()
方法:获取并移除此队列的头,如果此队列为空,则返回 null
。
#poll(long timeout, TimeUnit unit)
方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
#take()
方法:获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
#remove(Object o)
方法:从此队列中移除指定元素的单个实例(如果存在)。
4.1 poll
public E poll() {
// 获得锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获得头元素
return (count == 0) ? null : dequeue();
} finally {
// 释放锁
lock.unlock();
}
}
|
- 如果队列为空,则返回
null
,否则,调用 #dequeue()
方法,获取列头元素。
3.1.1 dequeue
private E dequeue() {
final Object[] items = this.items;
// 去除队首元素
E x = (E) items[takeIndex];
items[takeIndex] = null; // 置空
// 到达队尾,回归队头
if (++takeIndex == items.length)
takeIndex = 0;
// 总数 - 1
count--;
// 维护下迭代器
if (itrs != null)
itrs.elementDequeued();
// 通知阻塞在入列的线程
notFull.signal();
return x;
}
|
- 该方法主要是从列头(
takeIndex
位置)取出元素,同时如果迭代器 itrs
不为 null
,则需要维护下该迭代器。最后,调用 notFull
的 #signal()
方法,唤醒阻塞在入列线程。
4.2 可超时的 poll
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 获得锁
lock.lockInterruptibly();
try {
// <1> 若队列已空,循环等待被通知,再次检查队列是否非空
while (count == 0) {
// 可等待的时间小于等于零,直接返回 null
if (nanos <= 0)
return null;
// 等待,直到超时
nanos = notEmpty.awaitNanos(nanos); // 返回的为剩余可等待时间,相当于每次等待,都会扣除相应已经等待的时间。
}
// 出队
return dequeue();
} finally {
// 解锁
lock.unlock();
}
}
|
- 相比
#poll()
方法,增加了 <1>
处:
- 若队列已空,调用
notEmpty
的 #awaitNanos(long nanos)
方法,等待被通知(元素入列时,会调用 notEmpty
的 #signal()
方法,进行通知阻塞等待的出列线程)或者超时返回 null。
- 被通知后,再次检查队列是否为空。若非空,继续向下执行,否则继续等待被通知。
4.3 take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 获得锁
lock.lockInterruptibly();
try {
// <1> 若队列已空,循环等待被通知,再次检查队列是否非空
while (count == 0)
notEmpty.await();
// 出列
return dequeue();
} finally {
// 解锁
lock.unlock();
}
}
|
- 相比
#poll()
方法,增加了 <1>
处:
- 若队列已空,调用
notEmpty
的 #await()
方法,等待被通知(元素入列时,会调用 notEmpty
的 #signal()
方法,进行通知阻塞等待的出列线程)。
- 被通知后,再次检查队列是否为空。若非空,继续向下执行,否则继续等待被通知。
4.4 remove
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();
}
}
|
5. 补充说明
老艿艿:因为本文的重心在 ArrayBlockingQueue 的入队和出队,所以其他方法,例如迭代器等等,并未解析。所以,胖友,你懂的,自己研究哈。