挖掘队列源码之ArrayBlockingQueue
前言
上一段时间一直在探索线程池的路上,如今准备踏入到队列,还是按照一开始的阅读方式。探索ArrayBlockingQueue是基于JDK1.8,从注释上可以得知是一个由数组支持的有界阻塞队列,按照先进先出的顺序,新元素被插入到队列的尾部,从队列头部获取元素,提供在将新元素放入到饱满的队列中会导致阻塞,直到队列出现新的位置才会被唤醒继续往下操作的方法,还支持在从空队列中获取元素会导致阻塞,直到队列出现元素才会被唤醒的方法。有一个点需要注意下,ArrayBlockingQueue在某些情况下插入新元素时会执行相当于循环的操作,怎么表述呢,当在数组的最后一个索引处上插入新元素后,那么插入下一个新元素的索引将会是0,有点类似循环,而由于LinkedBlockingQueue不同的数据结构并不会发生这种情况,还有一点就是ArrayBlockingQueue内部中只有一个锁,所以对于读写来说,同时只能有一个操作在执行,要么是读要么是写,而LinkedBlockingQueue采用的是两个锁的读写分离,好了,接下来直接进入主题吧。
数据结构
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
// 用于存储插入的元素
final Object[] items;
// 下一个获取的索引
int takeIndex;
// 下一个插入的索引
int putIndex;
// 队列中元素的个数
int count;
// 可重入锁,防止多线程并发访问队列
final ReentrantLock lock;
// 从空队列中获取元素导致阻塞直到新元素插入时被唤醒
private final Condition notEmpty;
// 新元素放入到饱满的队列中导致阻塞直到队列出现空位置时被唤醒
private final Condition notFull;
// 迭代器
transient Itrs itrs = null;
}
构造函数
/**
* 初始化
* @param capacity 数组的大小
*/
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
/**
* 初始化
* @param capacity 数组的大小
* @param fair true:队列按照先进先出的顺序访问,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();
}
/**
* 按顺序插入集合中的元素并初始化
* @param capacity 数组的大小
* @param fair true:队列按照先进先出的顺序访问,false:不确定的顺序访问
* @param c 集合
*/
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();
}
}
简单方法
/**
* 循环递减i
* 若 i = 0 -> i = items.length - 1
* 若 i > 0 -> i = i -1
* @param i 变量
* @return 结果值
*/
final int dec(int i) {
return ((i == 0) ? items.length : i) - 1;
}
/**
* 获取指定索引处的元素
* @param i 指定索引
* @return 结果值
*/
final E itemAt(int i) {
return (E) items[i];
}
/**
* 插入元素
* @param x 元素
*/
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal(); // 已经插入元素了,应该唤醒等待获取元素的线程
}
/**
* 出队列(获取元素)
* @return 结果值
*/
private E dequeue() {
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;
}
/**
* 移除指定索引处的元素
* @param removeIndex
*/
void removeAt(final int removeIndex) {
final Object[] items = this.items;
/**
* 仔细观察可以发现 takeIndex ~ putIndex - 1 之间的元素都不为null,为了保证如此结构只有当removeIndex介于这两者之间时才需要将removeIndex后续的元素往前移动
* 而其他情况如 removeIndex == takeIndex 或 removeIndex == putIndex - 1只要将其置为null即可
* 但在else代码片段中不考虑其他调用代码的情况下还是有可能出现removeIndex < takeIndex 或 removeIndex > putIndex的情况,但这两种情况并不影响,实际上在调用代码中并未出现这两种情况
* 所以实际上只判断了 takeIndex < removeIndex < putIndex
*/
if (removeIndex == takeIndex) { // 当removeIndex == takeIndex时直接将其置为null即可
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
final int putIndex = this.putIndex;
for (int i = removeIndex;;) { // 在takeIndex < removeIndex < putIndex的情况下需要将removeIndex后续的元素往前移动
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal(); // 已经有元素出队列了,应该唤醒等待插入新元素的线程
}
/**
* 插入元素
* 实际上调用了offer方法,当队列饱满的情况下会抛出异常
* @param e 元素
* @return 是否插入成功
*/
public boolean add(E e) {
return super.add(e);
}
/**
* 插入元素
* 当队列饱满的情况下会返回false
* @param e 元素
* @return 结果值
*/
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();
}
}
/**
* 插入元素
* 当队列饱满的情况下会一直阻塞等待,直到被唤醒或被中断
* @param e 元素
* @return 结果值
*/
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // lock.lockInterruptibly与lock.lock的区别在于前者是可以发生中断的,也就是会直接抛出异常退出程序,而后者则不允许,两者都会锁住线程
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
/**
* 插入元素
* 当队列饱满的情况下会阻塞等待指定时间,直到被唤醒或被中断或超时
* @param e 元素
* @param timeout 指定时间
* @param unit 时间单位
* @return 结果值
*/
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();
}
}
/**
* 获取元素
* 当队列为空的情况下直接返回null
* @return 结果值
*/
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
/**
* 获取元素
* 当队列为空的情况下一直阻塞等待,直到被唤醒或被中断
* @return 结果值
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
/**
* 获取元素
* 当队列为空的情况下阻塞等待指定时间,直到被唤醒或被中断或超时
* @param timeout 指定时间
* @param unit 时间单位
* @return 结果值
*/
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();
}
}
/**
* 获取元素
* 与前面的三个方法较大不同,因为它调用的itemAt方法不会导致takeIndex索引自增,所以该方法即使调用多次依然能获取到元素,而其他三个方法则不行
* @return 结果值
*/
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
/**
* 移除元素
* @param o 元素
* @return 结果值
*/
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 { // 遍历查找 takeIndex ~ putIndex -1 之间的元素
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
/**
* 清空队列
*/
public void clear() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do { // 遍历队列并清空
items[i] = null;
if (++i == items.length)
i = 0;
} while (i != putIndex);
takeIndex = putIndex; // 细节部分,takeIndex = putIndex是为了下次插入新元素后可以获取到
count = 0;
if (itrs != null)
itrs.queueIsEmpty();
for (; k > 0 && lock.hasWaiters(notFull); k--)
notFull.signal();
}
} finally {
lock.unlock();
}
}
/**
* 将队列中的元素添加到集合中,添加指定个数
* 这些被添加到集合的元素将会使对应队列上的位置为null,相当于从队列拿出去了
* @param c 集合
* @param maxElements 指定元素个数
* @return 指定元素个数
*/
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = Math.min(maxElements, count); // 将队列中的元素添加到集合的个数,最小值是maxElements,最大值是count
int take = takeIndex;
int i = 0;
try {
while (i < n) { // 从takeIndex开始遍历获取元素
@SuppressWarnings("unchecked")
E x = (E) items[take];
c.add(x); // 即使add发生异常,那么发生异常之前添加的元素依然存在
items[take] = null;
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// 队列中的元素被添加到集合后,该队列对应索引上的元素就会变成null,所以这里也要更新其索引与元素个数
if (i > 0) {
count -= i;
takeIndex = take;
if (itrs != null) {
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
itrs.takeIndexWrapped();
}
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
}
// 关于迭代器的内容就不做讲解了...
总结
ArrayBlockingQueue提供了多种插入/获取元素的方法,先简单说下区别:
-
在队列饱满的情况下
插入新元素-
add:直接抛出异常。
-
offer:直接返回false。
-
put:一直阻塞等待直到被唤醒或线程被中断。
-
offer(time):阻塞等待指定时间,直到被唤醒或被中断或
超时。
-
-
在队列为空的情况下
获取元素-
poll:直接返回null。
-
take:一直被阻塞等待直到被唤醒或线程被中断。
-
poll(time):阻塞等待直到被唤醒或线程被中断或
超时。 -
peek:直接返回null,该方法即使调用多次依然能获取到元素,而其余3个方法则不行。
-
关于ArrayBlockingQueue的知识点并不是很多,很容易掌握!
浙公网安备 33010602011771号