ArrayBlockingQueue基于数组的数据结构实现,在其内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部坐标 takeIndex (消费数据坐标) 和尾部坐标 putIndex (生产数据坐标)。
ArrayBlockingQueue也被称为有界阻塞队列,顾名思义,就是明确有一个队列大小的。因此没有默认的构造函数,没有默认的队列大小。
// 声明队列大小的构造器 默认为非公平锁
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);
// 队列不为空时的Condition (相当于 wait(), notify())
notEmpty = lock.newCondition();
// 队列未满
notFull = lock.newCondition();
}
生产数据,即往队列中(数组中添加一个元素),ArrayBlockingQueue提供了四个方法。
第一个、第二个其实一样,都是offer方法。只不过add方法在队列已满时会抛出异常,根据业务不同可以选择不同方法。
// 第一个
public boolean add(E e) {
// 实际调用的offer方法
return super.add(e);
}
// super.add() 方法
public boolean add(E e) {
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 {
// 如果队列已经满了 直接返回添加失败 offer方法不会进行阻塞 put方法才会阻塞
if (count == items.length)
return false;
else {
// 队列没有满,则正常添加数据
enqueue(e);
return true;
}
} finally {
// 释放锁
lock.unlock();
}
}
第三个方法,指定等待时间插入元素,如果队列已满,如果指定时间内没有被其他线程唤醒,则插入失败。
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;
// 等待(挂起)指定时间后,如果没有在指定时间内被其他线程唤醒,则这里返回值小于0,直接返回插入失败
nanos = notFull.awaitNanos(nanos);
}
// 否则插入元素
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.await();
// 插入元素
enqueue(e);
} finally {
lock.unlock();
}
}
这四个方法都统一调用的同一个插入操作。
// 插入元素方法
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
// 插入元素
items[putIndex] = x;
// 插入元素后,计算插入坐标, 如果当前插入元素是数组最后一位,则下一次插入为数组第一个位置
if (++putIndex == items.length)
putIndex = 0;
count++;
// 插入元素后,则保证队列至少有一个元素,唤醒正在等待取元素的线程
notEmpty.signal();
}
接下来就是消费数据方法,相应的消费数据也有四个方法。
第一个方法是peek()方法,准确地说这个方法不是消费,而是查询,只是起到查看能消费的第一个数据,并没有从队列中移出元素。
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 返回当前队列第一个元素,并不移出,
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
final E itemAt(int i) {
return (E)items[i];
}
第二个方法是poll(),第三个方法是指定了时间的poll(long timeout, TimeUnit unit)方法。当队列为空时,取出的元素为null,不为空时,取出队列第一个元素,并从队列中移出。第三个方法会在队列为空时,等待指定时间再返回消费的结果。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
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;
// 等待指定时间 如果该时间内没有被唤醒(在插入元素时唤醒,即队列不为空时),则返回null
nanos = notEmpty.awaitNanos(nanos);
}
// 移出元素
return dequeue();
} finally {
lock.unlock();
}
}
第四个方法为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() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
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;
}