ArrayBlockingQueue
我们都知道这是一个队列,之前对这个类了解的不多,后来看了源码之后才发现与自己想的不一样。本文带大家了解ArrayBlockingQueue的工作原理。首先我们来了解这个类的成员们。
成员
// 存放元素的数组
final Object[] items;
// 下面这两个下标后面再说
int takeIndex;
int putIndex;
// count表示队列中的元素个数
int count;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
构造函数
初始化一个数组队列,要确定数组的大小。队列的大小一旦确定了,后面就不会在更改了。只能存放这么多的元素。
// 还记得上面ReentrantLock lock这个成员吗。这是一种锁,他里面分为公平锁
// 和非公平锁,fair这个参数就是用来确定用户选择的是公平锁还是非公平锁。本
// 文对ReentrantLock 这个类不做过多解释,有兴趣的可以去看下他的源码。
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();
}
这个构造函数在上面的基础上,将传递进来的元素集合,里面的元素一个一个加到队列里。
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) {
// 检查元素是否为null,如果是null会抛出异常
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
// putIndex就是最后一个元素的下一个空间的下标,如果元素已经
// 将队列放满了,那么putIndex从0开始。
} finally {
lock.unlock(); //打开锁
}
}
添加一个元素
add(E e) 方法和 offer(E e)方法
我们用队列添加一个元素,调用add(E e)方法,其实调用的就是offer(E e)方法。
public boolean add(E e) {
return super.add(e);
}
下面这个就是ArrayBlockingQueue父类的add(E e)方法,可以看到通过调用ArrayBlockingQueue的add方法实际上是调用了自己的offer方法。
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
checkNotNull(e); // 元素不能为null
final ReentrantLock lock = this.lock;
lock.lock(); // 上锁,保证了线程安全
try {
// 元素的个数不能超过队列的大小,若是队列放满了,那么直接返回false,不再继续存放元素。
if (count == items.length)
return false;
else {
// 队列没有放满,那么继续存放元素。
enqueue(e);
return true;
}
} finally {
lock.unlock();// 打开锁
}
}
// 入队列,将元素放入队列中,如果队列已满那么putIndex为0;
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
// 这个方法的作用是唤醒,说这个就要引出另一个添加元素的方法,看下面
notEmpty.signal();
}
put(E e)方法
同样是添加元素,那么put和上面的有什么不同呢。通过下面的代码,我们可以发现,最大的变化就是当队列满了之后,不再是返回false,而是将添加元素的线程阻塞了。只能等待别人的唤醒。
当队列满了之后,其后想要添加元素的线程都会被阻塞。只有等到某一个线程删除了队列里的元素,并唤醒阻塞队列。使得程序继续运行。
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();
}
}
删除一个元素
队列的删除元素就是从头开始一个一个删除。
poll()
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
删除元素,需要靠takeIndex这个下标来删除。takeIndex从0开始,删一个下标加1。当下标走到最后,又从0开始。
每删除一个元素,就会去唤醒一次阻塞队列
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;
}
take()
这个方法也是删除元素,它与上面不同的地方就是,如果队列为空,某个线程要删除元素,那么这个线程就会被阻塞。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
ps:唤醒有两种唤醒形式。
一种是因为删除时,队列为空而被阻塞的线程
一种是因为添加时,队列已满而被阻塞的线程
两种唤醒与阻塞相互对应。