上面的一篇文章,我们介绍了线程池的拒接策列,这篇文章开始我们介绍线程池任务队列中的等待队列—— ArraBlockingQueue。
先看看ArrayBlockingQueue的继承和实现的关系的图。
ArrayBlockingQueue是一个有界的队列,这个队列排列元素FIFO(先进先出),其中队列元素是用一个数组来保存的,创建时指定队列的大小,创建后,容量无法改变。
从上面的图可以看到,ArrayBlockingQueue是继承了AbstractQueue类,和实现了BlockingQueue接口。
看一下AbstractQueue中的方法:
AbstractQueue中的方法不多,就是几个的方法定义,添加元素和移除 元素,清空队列等…
再看看BlockQueue中的方法
BlockingQueue这个接口中的方法也跟AbsractQueue差不多,也是向队列添加元素,移除元素,清空队列等…
为什么AbstractQueue已经定义有相同操作的方法,还要实现BlockingQueue中的方法呢?其实这些方法的操作看起来一样,但是实际上是不一样的。下面会比较这些方法的不同点。
ArrayBlockingQueue有三个构造方法:
一、创建固定容量和使用默认访问策列的ArrayBlockQueue。
public ArrayBlockingQueue(int capacity)
二、创建一个固定容量和指定访问策列的ArrayBlockQueue。
public ArrayBlockingQueue(int capacity, boolean fair)
三、创建一个固定容量,指定访问策列,和给定最初的队列中的集合的元素。
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c)
ps:访问策列是用ReentrantLock来实现的,flase为非公平锁,true为公平锁,默认使用的是非公平锁,如果使用公平锁(true),队列会以FIFO的顺序授予线程的访问权限。如果使用非公平锁(false),访问顺序是未指定的,但是公平锁会降低吞吐量。
先来看看ArrayBlockingQueue中几个重要的元素
//用来存放元素的数组
final Object[] items;
//取出元素的下标
int takeIndex;
//插入元素的下标
int putIndex;
//队列中元素的个数
int count;
这里只介绍两个重要的方法 enqueue()跟 dequeue(),也就是入队跟出队的方法,because这两个方法最重要。
下面介绍一下向队列添加元素的三种方法,其中add()方法是属于AbstractQueue中的,put()和offer()是BlockingQueue中的。
其中只有put()会阻塞,add()和 offer()是不阻塞的。
看一下它们的源码
put():
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();
}
}
再看看入队的具体代码 也就是enqueue()方法:
private void enqueue(E x) {
//获取当前队列
final Object[] items = this.items;
//添加进队列中
items[putIndex] = x;
//判断进队后的长度时候与队列长度相等。
if (++putIndex == items.length)
//相等就让插入元素的位置为0
putIndex = 0;
//队列元素个数+1
count++;
//唤醒其他阻塞出队的队列
notEmpty.signal();
}
ps:当队列元素已经满的状态下,其实这个队列变成一个环形队列,就是入队给跟出队的下标都相等。
offer()的源码,其实过程跟put()方法差不多,但是offer()方法不会阻塞等待。
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();
}
}
add()方法实际上也是调用 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");
}
父类调用自己的offer()方法,但是父类中的offer()方法是个抽象等方法,它并没有实现,所以最后它还是调用了子类的offer()方法。
入队的方法这么多,出队的方法肯定不止一个啦!ArrayBlockingQueue出队的方法有:poll(),take(),peek()。
这些出对方法中只有 take()方法会阻塞,poll()和 peek()方法不会。
看看take()方法的源码先。
public E take() throws InterruptedException {
// 获得锁
final ReentrantLock lock = this.lock;
//上锁,可中断的锁
lock.lockInterruptibly();
try {
//当队列元素为空的时候,就阻塞。
while (count == 0)
//阻塞吧
notEmpty.await();
//队列元素不为空,出队。
return dequeue();
} finally {
//解锁
lock.unlock();
}
}
在看看出队的具体操作方法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;
}
poll()方法,调用 dequeue()方法。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
peek()方法
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex);
} finally {
lock.unlock();
}
}
poll()跟 peek()方法不一样。
poll()方法是检索并删除此队列的头,如果此队列为空,则返回 null 。
peek()方法检索但不删除此队列的头,如果此队列为空,则返回 null 。
一个是删除元素,一个是不删除元素。
上面就介绍了enqueue()方法跟dequeue()方法,ArrayBlockingQueue这个阻塞队列并不难理解,如果想了解更清楚一点就直接看源码。