概述
基于数组的有界阻塞队列。
1)FIFO;
2)ArrayBlockingQueue是经典的有界缓存的实现,其内是固定大小的数组缓存,生产者向其中插入元素,消费者从其取出元素;
3)支持公平锁,默认为非公平锁,公平锁会降低吞吐量但更稳定,避免某个线程阻塞不工作的情况;
数据结构
Object数组,逻辑上是环形数组:
/** The queued items */
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
// 经典的一把锁两个条件的并发控制
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
构造器
// 带容量参数构造,默认为非公平锁
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(); // Queue非空
notFull = lock.newCondition(); // Queue未满
}
// 带容量、是否为公平锁参数、Collection参数构造
// 将Collection中的元素添加进来
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // 为可见性加锁,非互斥
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();
}
}
增删查
基础方法
// 循环增加
final int inc(int i) {
return (++i == items.length) ? 0 : i;
}
// 将元素循环入队
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal(); // 发送队列非空信号
}
// 将元素循环出队
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal(); // 发送队列未满信号
return x;
}
增
步骤:
1)竞争获取lock;
2)若队列未满,则才将元素入队,发送队列非空信号;
3)若队列已满,offer立即返回false,add立即抛出IllegalStateException异常,put一直等到队列未满,offer阻塞版则等待timeout时间,超出则立即返回false;
4)释放锁。
// offer的抛出异常版本IllegalStateException
public boolean add(E e) {
return super.add(e);
}
// 将元素e队列
// 非阻塞
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length) // 队列已满,立即返回false
return false;
else {
insert(e); // 队列未满,将元素入队,返回true
return true;
}
} finally {
lock.unlock();
}
}
// 将元素e队列
// 无限阻塞
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取到锁前,可中断的锁的获取
try {
while (count == items.length) // 队列已满,则一直wait
notFull.await();
insert(e); // 直到等到队列未满时,才将元素入队,发送队列非空信号
} finally {
lock.unlock();
}
}
// 将元素e队列
// 有限阻塞,时长为timeout
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) { // 队列已满,则等待timeout,超出则立即返回
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
insert(e); // 等待小于timeout,则将元素入队,发送队列非空信号
return true;
} finally {
lock.unlock();
}
}
删
步骤:
1)竞争获取lock;
2)若队列非空,则将队首元素出队,发送队列未满信号;
3)若队列为空,poll立即返回null,remove立即抛出NoSuchElementException异常,take一直等到队列非空,poll阻塞版则等待timeout时间,超出则立即返回null;
4)释放锁。
// 将队首元素出队
// 非阻塞
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract(); // 队列为空,返回null;否则将队首元素出队,发送队列未满信号
} finally {
lock.unlock();
}
}
// 将队首元素出队
// 无限阻塞
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取到锁前,可中断的锁的获取
try {
while (count == 0) // 队列为空,则一直wait
notEmpty.await();
return extract(); // 直到等到队列非空时,才将队首元素出队,发送队列未满信号
} finally {
lock.unlock();
}
}
// 将队首元素出队
// 有限阻塞
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) { // 队列为空,则等待timeout,超出则立即返回null
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return extract(); // 等待小于timeout,则将将队首元素出队,发送队列未满信号
} finally {
lock.unlock();
}
}
查
步骤:
1)竞争获取lock;
2)队列为空,返回null;否则队首元素;
3)释放锁。
// 获取队首元素
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取lock
try {
return (count == 0) ? null : itemAt(takeIndex); // 队列为空,返回null;否则队首元素
} finally {
lock.unlock(); // 释放lock
}
}
迭代器
弱一致性。
/**
* Iterator for ArrayBlockingQueue. To maintain weak consistency
* with respect to puts and takes, we (1) read ahead one slot, so
* as to not report hasNext true but then not have an element to
* return -- however we later recheck this slot to use the most
* current value; (2) ensure that each array slot is traversed at
* most once (by tracking "remaining" elements); (3) skip over
* null slots, which can occur if takes race ahead of iterators.
* However, for circular array-based queues, we cannot rely on any
* well established definition of what it means to be weakly
* consistent with respect to interior removes since these may
* require slot overwrites in the process of sliding elements to
* cover gaps. So we settle for resiliency, operating on
* established apparent nexts, which may miss some elements that
* have moved between calls to next.
*/
private class Itr implements Iterator<E> {
private int remaining; // Number of elements yet to be returned
private int nextIndex; // Index of element to be returned by next
private E nextItem; // Element to be returned by next call to next
private E lastItem; // Element returned by last call to next
private int lastRet; // Index of last element returned, or -1 if none
Itr() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
lastRet = -1;
if ((remaining = count) > 0)
nextItem = itemAt(nextIndex = takeIndex);
} finally {
lock.unlock();
}
}
public boolean hasNext() {
return remaining > 0;
}
public E next() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
if (remaining <= 0)
throw new NoSuchElementException();
lastRet = nextIndex;
E x = itemAt(nextIndex); // check for fresher value
if (x == null) {
x = nextItem; // we are forced to report old value
lastItem = null; // but ensure remove fails
}
else
lastItem = x;
while (--remaining > 0 && // skip over nulls
(nextItem = itemAt(nextIndex = inc(nextIndex))) == null)
;
return x;
} finally {
lock.unlock();
}
}
public void remove() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
int i = lastRet;
if (i == -1)
throw new IllegalStateException();
lastRet = -1;
E x = lastItem;
lastItem = null;
// only remove if item still at index
if (x != null && x == items[i]) {
boolean removingHead = (i == takeIndex);
removeAt(i);
if (!removingHead)
nextIndex = dec(nextIndex);
}
} finally {
lock.unlock();
}
}
}
特性
数组+有界+阻塞队列。
ArrayBlockingQueue用一把ReentrantLock锁,两个条件notEmpty、notFull,以让增加、删除操作之间互相协作,保证队列中有元素可取、有空间可插入,实现相对于缓存的功能。而在ConcurrentHashMap中增、删、改之间是互斥的,它们的并发意义是不一样的。
疑问
数组的可见性如何在并发中体现出来?ConcurrentHashMap、ArrayBlockingQueue各不相同。