前面我们介绍的LinkedBlockingQueue的源码,这篇文章我们一起研究下ArrayBlockingQueue的源码
(1)LinkedBlockingQueue源码解析
(2)ArrayBlockingQueue源码解析
从语义上看,ArrayBlockingQueue底层是基于数组实现的,而LinkedBlockingQueue底层是基于链表实现的(实际是封装为Node,类似C语言的节点),两者实现方式的不同,也造成了性能和效率的差异。
我们首先来介绍下几个重要的类变量:
/**实际承载队列元素的数组 */
final Object[] items;
/** 消费者序列号 for next take, poll, peek or remove */
int takeIndex;
/**生产者序列号 for next put, offer, or add */
int putIndex;
/** 队列大小 */
int count;
/*
* 并发的控制采用两种condition的逻辑,也就是俗称的”两把锁“
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** 消费者锁 */
private final Condition notEmpty;
/** 生产者锁 */
private final Condition notFull;
下面看几个核心的私有方法:
/**
* 入队时的核心方法,block内部维护了一个putIndex,用来标示
* 当前允许插入的下标
* 一旦插入后,等待notEmpty的线程就被唤醒,即可以消费了
*/
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
/**
* 出对时的核心方法,同insert,block内部维护了takeIndex
* 用来标示当前允许出对的下标
* 而一旦出对,等待notFull的线程也被唤醒,即可以生产了
*/
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;
}
可见,ArrayBlockingQueue的入队和出对,都是基于数组的实现,而LinkedBlockingQueue则是对元素封装成Node后,再进行其他操作,所以在大量线程操作队列时,ArrayBlockingQueue的性能要优于LinkedBlockingQueue。
ArrayBlockingQueue是固定大小的队列,初始化时,必须要传入队列的容量。
下面我们列举ArrayBlockingQueue提供的几个队列方法
一 offer
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
insert(e);
return true;
}
} finally {
lock.unlock();
}
}
offer提供了两个重载的方法,上面的这个是不带参数的,如果当前队列已饱和,则直接返回false,而另一个offer方法中可传入等待时间,如果在指定的等待时间内,仍然等不到队列有空的位置,则直接返回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);
}
insert(e);
return true;
} finally {
lock.unlock();
}
}
二 put
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
可见put方法在队列饱和时,是阻塞的,除非队列不饱和
三 poll和take
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
poll和take类似,不同点在于poll在队列空时,是不阻塞的,但是take会一直阻塞直到队列非空
四 peek
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : itemAt(takeIndex);
} finally {
lock.unlock();
}
}
peek方法只是返回队首元素,但是不移除
此外,还提供了一些实用的方法,toArray(将队列转换为数组),clear(清空队列),drainTo(将队列转移到集合中)