ArrayBlockingQueue源码分析,ArrayBlockingQueue实现了哪些接口?
public class ArrayBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable
直接实现了BlockingQueue、Serializable接口,继承了AbstractQueue类,AbstractQueue继承了AbstractCollection抽象类,从而拥有了部分集合的功能。
ArrayBlockingQueue主要变量分析:
E[] items:元素存放在此数组当中,也就是说此类的数据缓存使用的数据结构是数组,由类名也可以看出。
int takeIndex:记录目前取到了数组中的第几个元素的下标
int putIndex:要在数组中插入元素的位置下标,插入一个元素,值加1,当值等于数组长度时,值重置为0,达到循环利用数组的作用。
int count:队列中目前有多少个元素
ReentrantLock lock:此类之所以是线程安全的,是因为使用了锁,也就是这个变量
Condition notEmpty:当数组中没有元素,调用take方法阻塞后,就是通过此条件唤醒的
Condition notFull: 当数组中元素已满调用put方法阻塞后,通过此条件唤醒的
看完了主要变量,还是从构造方法开始这个类的方法分析。
构造方法有两个,一个入参和两个入参的,直接看两个入参的,因为一个入参的底层也是会调用到两个入参的。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = (E[]) new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
构造方法中:(1):初始化了数组的大小;(2)创建了一个锁对象,这个锁默认是不公平锁(唤醒线程时不按照等待时间长短获得所,按照先抢到谁先获得锁);(3)创建两个条件类。
这个类有两大类操作,放元素和取元素。每类操作又有阻塞操作和非阻塞操作。
非阻塞放元素的方法有:add()和offer(),两个方法的区别是add()放元素时,若队列已满,则会抛异常,而offer()方法则会返回false。
非阻塞取元素的方法有:poll()
阻塞放元素的方法有:put();
阻塞取元素的方法有:take();
接下来我们主要分析下阻塞放、取元素,这两个方法明白了之后,其他的方法都很容易理解。
先看阻塞放元素的put方法源码:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
} finally {
lock.unlock();
}
}
首先判断队列中的元素个数是否达到了初始化数组的大小,若达到了,说明队列满了,则阻塞等待;否则,执行insert方法插入元素。insert方法源码:
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
final int inc(int i) {
return (++i == items.length)? 0 : i;
}
insert主要的工作有:(1)在putIndex下标处插入元素;(2)putIndex加1,若达到数组长度,则重置为0;(3)count加1;(4)唤醒执行take方法时阻塞的线程
阻塞取元素方法take方法:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == 0)
notEmpty.await();
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
throw ie;
}
E x = extract();
return x;
} finally {
lock.unlock();
}
}
首先判断队列中元素是否为0,如果为0,则调用notEmpty.await()阻塞,直到队列中有元素的时候被notEmpty.signal()唤醒;如果队列中元素不为0,则调用extract()方法获取元素。extract()源码:
private E extract() {
final E[] items = this.items;
E x = items[takeIndex];
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
extract()方法主要做的事情有:(1)根据takeIndex记录的下标从数组中取元素,最开始时值为0;(2)取到元素后将对应的位置置空;(3)takeIndex值加1,若达到数组长度,值重置为0,重新从0开始取,跟putIndex用法一样;(4)count值减1;(5)唤醒执行put方法时阻塞的线程
总结:ArrayBlockingQueue从名字可以看出来,底层存元素的结构是数组,有阻塞功能,阻塞功能是通过两个Condition进行协调来实现生产消费模式的。因为读取元素之前又会使用ReentrantLock进行同步,所以它又是线程安全的。