前言
ArrayBlockingQueue从名字上我们就可以知道:以数组实现的阻塞队列。它是线程安全的,满足队列的特性:先进先出。下面我们来分析下它的源码,了解下它的实现过程。
1、属性
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;
}
2、构造方法
public ArrayBlockingQueue(int capacity) {
//初始化队列容量,默认非公平锁
this(capacity, 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();
}
//初始化队列容量,指定是否是公平锁,初始化容量
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
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();
}
}
3、入队
入队有四个方法,分别是add(E e)、offer(E e)、put(E e)、offer(E e, long timeout, TimeUnit unit),下面来看看它们的区别:
public boolean add(E e) {
//调用父类的add(e)方法
return super.add(e);
}
//super.add(e)方法:
public boolean add(E e) {
// 调用offer(e)方法,如果成功返回true
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
//检查元素是否为空
checkNotNull(e);
final ReentrantLock lock = this.lock;
//获取独占锁,保证线程安全
lock.lock();
try {
//如果数组满了
if (count == items.length)
//返回false
return false;
else {
//数组没满,就入队列
enqueue(e);
return true;
}
} finally {
//释放独占锁
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//加锁,线程中断抛出异常
lock.lockInterruptibly();
try {
while (count == items.length)
//数组满了,notfull需要等待数组中取出一个元素后才能操作
//进行等待,这里可能有多个线程阻塞在lock上面
notFull.await();
//入队列
enqueue(e);
} finally {
lock.unlock();
}
}
//这里多加了一个等待超时机制,其它和put一样
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();
}
}
//入队列:利用指针的循环来存放元素
private void enqueue(E x) {
final Object[] items = this.items;
// 把元素放在放指针的位置上
items[putIndex] = x;
// 如果放指针等于数组长度,就返回头部
if (++putIndex == items.length)
putIndex = 0;
// 数组数量加1
count++;
// 入队了一个元素,所以唤醒notEmpty去取出元素
notEmpty.signal();
}
4、出队
出队也有四个方法,分别是remove()、poll()、take()、poll(long timeout, TimeUnit unit),我们也来看看它们的区别:
public E remove() {
// 调用poll()方法出队,返回出队的元素
E x = poll();
if (x != null)
return x;
else
//没有出队的则抛出异常
throw new NoSuchElementException();
}
public E poll() {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
//队列个数为0,则返回null,否则出队
return (count == 0) ? null : dequeue();
} finally {
//释放锁
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 加锁
lock.lockInterruptibly();
try {
// 队列个数为0
while (count == 0)
//阻塞等待在条件notEmpty上
notEmpty.await();
//有元素,则出队
return dequeue();
} finally {
// 解锁
lock.unlock();
}
}
//同take,加了阻塞的超时时间
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();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
// 取出“取指针”位置的元素
E x = (E) items[takeIndex];
// 设置“取指针”位置为null
items[takeIndex] = null;
// 取指针前移,若到了数组最大长度
if (++takeIndex == items.length)
//则,再次返回数组的头部位置0
takeIndex = 0;
// 元素数量减1
count--;
if (itrs != null)
itrs.elementDequeued();
//唤醒notFull条件
notFull.signal();
return x;
}
5、总结
- ArrayBlockingQueue长度是固定的,在初始化的时候指定,所以要慎重考虑长度。
- ArrayBlockingQueue是线程安全的,利用了ReentrantLock和两个Condition条件来保证并发安全。
- ArrayBlockingQueue在入队和出队都分别定义了:抛出异常,有返回值,阻塞,超时,四类方法来保证不同的场景用途。
结束语
前一篇,我们学习了ConcurrentHashMap的分段锁,出队和入队是否可以使用分段锁,如果让你实现,你怎么实现呢?
如果你觉得本篇文章对你有帮助的话,请帮忙点个赞,再加一个关注。