目录
一、简介
前面一篇文章我们学习了LinkedBlockingQueue,本篇文章我们介绍另外一种阻塞队列ArrayBlockingQueue。
ArrayBlockingQueue是一个有界阻塞队列,底层基于数组来实现的。队列的头是队列中存在时间最长的元素,队列的尾部是队列中时间最短的元素,并且新元素插入到队列的尾部。
ArrayBlockingQueue一旦创建,容量就不能更改。
ArrayBlockingQueue支持一个可选的公平性策略,用于对正在等待的生产者和消费者线程进行排序。默认情况下,不保证这种顺序,但是,将公平性设置为true的队列将按FIFO顺序授予线程访问权,公平性通常会降低吞吐量,但会降低可变性并避免饥饿现象。
二、ArrayBlockingQueue常用API
【a】构造方法
ArrayBlockingQueue(int capacity) 使用给定的(固定的)容量和默认访问策略创建ArrayBlockingQueue |
ArrayBlockingQueue(int capacity, boolean fair) 使用给定的(固定的)容量和指定的访问策略创建ArrayBlockingQueue |
ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) 创建一个ArrayBlockingQueue,该队列具有给定的(固定的)容量、指定的访问策略,最初包含给定集合的元素,以集合迭代器的遍历顺序添加。 |
【b】常用方法
方法返回值 | 方法描述 |
boolean | 如果可以在不超过队列容量的情况下立即插入指定的元素,成功后返回true,如果队列已满则抛出IllegalStateException,则在此队列的末尾插入指定的元素。 |
void | clear() 自动删除此队列中的所有元素 |
boolean | 如果此队列包含指定的元素,则返回true。 |
int | drainTo(Collection<? super E> c) 从该队列中删除所有可用元素,并将它们添加到给定集合中 |
int | drainTo(Collection<? super E> c, int maxElements) 从该队列中最多删除给定数量的可用元素,并将它们添加到给定集合中 |
iterator() 按适当的顺序返回此队列中元素的迭代器 | |
boolean | 如果可以在不超过队列容量的情况下立即插入指定的元素,则在队列末尾插入,如果成功则返回true,如果队列已满则返回false |
boolean | offer(E e, long timeout, TimeUnit unit) 将指定的元素插入到此队列的末尾,如果队列已满,则在指定的等待时间之前等待空间可用 |
peek() 检索但不删除此队列的头,或在此队列为空时返回null | |
poll() 检索并删除此队列的头,如果此队列为空,则返回null | |
poll(long timeout, TimeUnit unit) 检索并删除此队列的头,如果元素变得可用,则需要等待指定的等待时间 | |
void | 将指定的元素插入到此队列的末尾,等待队列满时可用的空间 |
int | 返回此队列在理想情况下(在没有内存或资源约束的情况下)可以不阻塞地接受的附加元素的数量。 |
boolean | 如果指定元素存在,则从此队列中移除该元素的单个实例 |
int | size() 返回此队列中的元素数量 |
take() 检索并删除此队列的头,如有必要则等待,直到某个元素可用为止 | |
Object[] | toArray() 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列 |
<T> T[] | toArray(T[] a) 返回一个数组,该数组包含此队列中的所有元素,按适当的顺序排列;返回数组的运行时类型是指定数组的运行时类型 |
toString() 返回此集合的字符串表示形式 |
三、ArrayBlockingQueue使用案例
下面通过一个简单的生产者-消费者案例来说明ArrayBlockingQueue的使用方法:
public class T03_ArrayBlockingQueue {
/**
* 创建初始容量为10的ArrayBlockingQueue
*/
private static BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10);
private static Random random = new Random();
public static void main(String[] args) {
//两个生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 3; j++) {
try {
/**
* put()方法是如果容器满了的话就会把当前线程挂起
* offer()方法是容器如果满的话就会返回false。
*/
blockingQueue.put("A" + j);
System.out.println(Thread.currentThread().getName() + "---put---" + ("A" + j));
TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者" + i).start();
}
//三个消费者线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
for (; ; ) {
try {
/**
* take()方法和put()方法是对应的,从中拿一个数据,如果拿不到线程挂起
* poll()方法和offer()方法是对应的,从中拿一个数据,如果没有直接返回null
*/
System.out.println(Thread.currentThread().getName() + "---take---" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者" + i).start();
}
}
}
运行结果:
消费者0---take---A0
生产者0---put---A0
生产者1---put---A0
消费者0---take---A0
生产者0---put---A1
消费者2---take---A1
生产者0---put---A2
消费者0---take---A2
生产者1---put---A1
消费者1---take---A1
生产者1---put---A2
消费者2---take---A2
四、ArrayBlockingQueue源码阅读
ArrayBlockingQueue底层是使用一个数组实现队列的,并且在构造ArrayBlockingQueue时需要指定容量,所以一旦创建,容量就不能改变了,因此ArrayBlockingQueue是一个容量限制的阻塞队列。因此,在队列全满时执行入队将会阻塞,在队列为空时出队同样将会阻塞。
重要属性说明:
/**
* 序列化ID
*/
private static final long serialVersionUID = -817911632652898426L;
/** 对象数组,用于存放队列中的元素 */
final Object[] items;
/** 下一次 take, poll, peek or remove的元素索引 */
int takeIndex;
/** 下一次next put, offer, or add的元素索引 */
int putIndex;
/** 队列中元素的数量 */
int count;
/** 可重入的锁 */
final ReentrantLock lock;
/** 等待出队takes的条件对象 */
private final Condition notEmpty;
/** 等待入队puts的条件对象 */
private final Condition notFull;
三个构造方法:
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();
}
//使用给定的(固定的)容量、指定的访问策略创建一个ArrayBlockingQueue,并最初包含给定集合的元素,以集合迭代器的遍历顺序添加元素。
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) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
下面对一些常用的方法进行说明:
【a】add(E e) :如果可以在不超过队列容量的情况下立即插入指定的元素,成功后返回true,如果队列已满则抛出IllegalStateException,则在此队列的末尾插入指定的元素.
//add()方法满了直接抛出Queue full队列已满异常
public boolean add(E e) {
//调用父类AbstractQueue中的方法
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
//如果队列已满,将会抛出IllegalStateException异常.
throw new IllegalStateException("Queue full");
}
【b】offer(E e)、offer(E e, long timeout, TimeUnit unit):
/**
* 如果可以在不超过队列容量的情况下立即插入指定的元素,则在队列末尾插入,如果成功则返回true,如果队列已满则返回false。此方法通常比add方法更好,后者仅通过抛出异常就不能插入元素。
*/
public boolean offer(E e) {
//如果参数为空,则抛出NullPointerException
checkNotNull(e);
//获取锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//如果队列已满,直接返回false
if (count == items.length)
return false;
else {
//如果队列未满,执行入队操作
enqueue(e);
return true;
}
} finally {
//释放锁
lock.unlock();
}
}
/**
* 在当前put位置插入元素.
* 只有在持有锁时才调用.
*/
private void enqueue(E x) {
final Object[] items = this.items;
//设置值
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//通知消费者进行消费
notEmpty.signal();
}
//与offer的区别就是可以指定超时时间
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//如果参数为空,则抛出NullPointerException
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
//如果超过指定的限制时间还未能成功入队,直接返回false.
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
//执行入队操作,返回true
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
【c】put(E e)
/**
* 将指定的元素插入到此队列的末尾,等待队列满时可用的空间.
*/
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();
}
}
【d】poll()、poll(long timeout, TimeUnit unit)
/**
* 检索并删除此队列的头,如果此队列为空,则返回null
*/
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果队列为空,直接返回null,否则执行出队操作
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
/**
* 提取元素当前的位置.
* 只有在持有锁时才调用.
*/
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;
}
//可指定超时时间,原理与poll()一样
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) {
//如果超时了,直接返回null
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
【e】take()
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
//如果当前队列为空,则阻塞当前线程
notEmpty.await();
//出队操作
return dequeue();
} finally {
lock.unlock();
}
}
【f】remove(Object o)
/**
* 如果指定元素存在,则从此队列中移除该元素的单个实例。更正式地说,如果队列包含一个或多个这样的元素,则删除一个元素e,比如o.equals(e)。如果此队列包含指定的元素,则返回true
(或者,如果这个队列因为调用而改变)。删除基于循环数组的队列中的内部元素本质上是一种缓慢且具有破坏性的操作,因此应该只在例外情况下进行,理想情况下,只有在已知队列不能被其他线程访问的情况下才进行。
*/
public boolean remove(Object o) {
//如果需要删除的元素为空,直接返回false
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
//循环队列,挨个进行比较,可以看到使用equals进行相等判断
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
【g】peek()
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//队列为空时为空
return itemAt(takeIndex);
} finally {
lock.unlock();
}
}
//直接根据下标获取数组对应的值
final E itemAt(int i) {
return (E) items[i];
}
五、总结
ArrayBlockingQueue内部使用对象数组实现,并且数组的长度是有界的,所以ArrayBlockingQueue也是有界阻塞队列。ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。ArrayBlockingQueue内部使用了一把锁对象,所以要么只有一个线程在入队,要么只有一个线程在出队(这一点与上一节的LinkedBlockingQueue不一样)。此外需要注意的是,创建ArrayBlockingQueue必须指定初始容量,而且还可以指定是否采用公平锁,默认采用非公平锁策略。