1 概述
ArrayBlockingQueued是数组实现的线程安全的有界的阻塞队列。队列按照先进先出(FIFO)的原则对元素进行排序。
通过以下关键词分析我们来更深入理解ArrayBlockingQueue
1.1 如何理解“队列”
队列这个概念非常好理解。你可以把它想象成排队买票,先来的先买,后来的人只能站末尾,不允许插队。先进者先出,这就是典型的“队列”。
相对于栈只支持两个基本操作:入栈 push()和出栈 pop(),对于也只支持两个操作入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素,因此队列跟栈一样,也是一种操作受限的线性表数据结构
1.2 如何理解“线程安全的”
在多线程情况下,会有多个线程同时操作队列,这个时候就会存在线程安全问题,线程安全的队列我们叫作并发队列.
那如何实现一个线程安全的队列呢?
方式一
最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。
方式二
利用 CAS 原子操作,可以实现非常高效的并发队列。
1.3 阻塞队列
阻塞队列其实就是在队列基础上增加了阻塞操作。简单来说,就是在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。
1.4 有界队列
有界队列表示队列中存储数据是有限,如果队列满后在次向队列中添加数据会失败或阻塞。
2 实现一个"队列"
我们知道了,队列跟栈一样,也是一种抽象的逻辑存储结构。它具有先进先出的特性,支持在队尾插入元素,在队头删除元素。如果要想实现一个队列可以用数组来实现,也可以用链表来实现,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列。
2.1 顺序队列
2.1.1 实现原理
比起栈的数组实现,队列的数组实现稍微有点儿复杂,复杂在哪呢?对于栈来说,我们只需要一个栈顶指针就可以了。但是队列需要两个指针:一个是 head 指针,指向队头;一个是 tail 指针,指向队尾。结合下面这幅图来理解。当 a、b、c、d 依次入队之后,队列中的 head 指针指向下标为 0 的位置,tail 指针指向下标为 4 的位置。
当我们调用两次出队操作之后,队列中 head 指针指向下标为 2 的位置,tail 指针仍然指向下标为 4 的位置。
你肯定已经发现了,随着不停地进行入队、出队操作,head 和 tail 都会持续往后移动。当 tail 移动到最右边,即使数组中还有空闲空间,
也无法继续往队列中添加数据了。这个问题该如何解决呢?使用循环队列
2.3 循环队列
循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。
我们可以看到,图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,我们放入下标为 7 的位置。但这个时候,我们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,我们将 b 放入下标为 0 的位置,然后 tail 加 1 更新为 1。所以,在 a,b 依次入队之后,循环队列中的元素就变成了下面的样子:
通过这样的方法,我们成功避免了数据搬移操作。但是循环队列最关键的是,确定好队空和队满的判定条件。
在用数组实现的非循环队列中,队满的判断条件是 tail == n,队空的判断条件是 head == tail。那针对循环队列,如何判断队空和队满呢?
方式一
就像我图中画的队满的情况,tail=3,head=4,n=8,所以总结一下规律就是:(3+1)%8=4。多画几张队满的图,你就会发现,当队满时满足以下公式 :(tail+1)%n=head。
当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。
3 ArrayBlockingQueue源码解析
3.1 类结构
3.2 实现原理
1 ArrayBlockingQueue使用循环数组实现顺序队列,
2 ArrayBlockingQueue内部存在着一个可重入的锁,当出队和入队操作时首先要获取同一把锁来保证线程安全。数组实现的队列出队和入队是要相互排斥的,因为数组删除元素需要对数据中元素进行位移!
3 ArrayBlockingQueue内部存在着一个可重入的锁,同时此锁生成二个等待队列Condition(notFull,notEmpty) -- 生产者和消费者模式
在入队时需要获取锁,获取锁成功后会判断队列是否已满(数组已满)。如果队列已满会将当前线程添加到notFull等待队列中等待唤醒。如果队列没有满则入队,并释放notEmpty等待队列中一个等待的线程。
在出队时需要获取锁,获取锁成功后会判断队列是否为空(数组为空)。如果队列为空会将当前线程添加到notEmpty等待队列中等待唤醒。如果队列不为空则出队,并释放notFull等待队列中一个等待的线程。
3.3 核心属性
// 内部数组
final Object[] items;
// 重入锁
final ReentrantLock lock;
// 条件,用于出队等待队列
private final Condition notEmpty;
// 条件,用于入队等待队列
private final Condition notFull;
// 出队位置
int takeIndex;
// 入队位置
int putIndex;
//队列中元素数量
int count;
3.4 构造函数
//创建一个带有给定的(固定)容量和默认访问策略的 ArrayBlockingQueue。
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
//创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue。
//fair 用来表示获取锁的方式是否公平[(详见ReentrantLock)](https://note.youdao.com/)
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,它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。
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,NullPointerException
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
3.5 入队操作
将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。
public boolean add(E e) {
if (offer(e)){
return true;
}else{
// 抛出异常...
}
}
将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
public boolean offer(E e) {
//插入的元素是否为null,是抛出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();
}
}
//添加元素到队列尾部
private void enqueue(E x) {
//获取队列中数组对象
final Object[] items = this.items;
//在items[putIndex]插入元素
items[putIndex] = x;
//putIndex向数组尾部循环移动
if (++putIndex == items.length)
putIndex = 0;
//队列元素数量+1
count++;
//释放notEmpty等待队列中阻塞的线程
notEmpty.signal();
}
将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间,在成功时返回 true。
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//插入的元素是否为null,是抛出NullPointerException异常
checkNotNull(e);
//获取等待的时间
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//获取独占锁,成功返回,失败则阻塞,可响应中断
lock.lockInterruptibly();
try {
//如果队列已满,等待时间大于0。将当前线程添加notFull等待队列,并到限时阻塞当前线程
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
//添加元素到队列尾部
enqueue(e);
//成功返回true
return true;
} finally {
//释放锁
lock.unlock();
}
}
将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间
public void put(E e) throws InterruptedException {
//插入的元素是否为null,是抛出NullPointerException异常
checkNotNull(e);
final ReentrantLock lock = this.lock;
//获取锁,失败则阻塞,可响应中断。
lock.lockInterruptibly();
try {
//如果队列已满。将当前线程添加notFull等待队列,并阻塞当前线程
while (count == items.length){
notFull.await();
}
//添加元素到队列
enqueue(e);
} finally {
//释放锁
lock.unlock();
}
}
3.6 出队操作
获取并移除此队列的头,如果此队列为空,则返回 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;
//获取items[takeIndex]元素
E x = (E) items[takeIndex];
//将数组items[takeIndex]元素设置为null
items[takeIndex] = null;
//takeIndex向后循环移动
if (++takeIndex == items.length)
takeIndex = 0;
//队列数量--
count--;
if (itrs != null)
itrs.elementDequeued();
//释放等待notFull等待队列中阻塞的线程
notFull.signal();
//返回
return x;
}
获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
//获取等待的时间
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//获取锁,失败则阻塞,可响应中断。
lock.lockInterruptibly();
try {
//如果队列为空,等待时间大于0,将当前线程添加notEmpty等待队列,并限时阻塞当前线程
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
//返回队列头部元素
return dequeue();
} finally {
//释放锁
lock.unlock();
}
}
获取并移除此队列的头部,在元素变得可用之前一直等待
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//获取锁,失败则阻塞,可响应中断。
lock.lockInterruptibly();
try {
//如果队列为空,将当前线程添加notEmpty等待队列张,并阻塞当前线程
while (count == 0){
notEmpty.await();
}
//返回队列头部元素
return dequeue();
} finally {
//释放锁
lock.unlock();
}
}
指定元素出队
- 从此队列中移除指定元素的单个实例
- 遍历队列数组中元素所有元素找到指定元素在数组的下标
-
如果删除元素刚好位置和takeIndex重合,直接删除,takeIndex移动
-
删除元素b刚好位置在takeIndex和putIndex之间,直接删除,并将数组向前平移。
public boolean remove(Object o) {
//删除元素为null,直接返回失败
if (o == null){
return false;
}
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
//获取锁,失败则阻塞
lock.lock();
try {
// 遍历内部数组的所有元素,从 takeIndex 开始,找到删除元素的下标位置
for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) {
if (o.equals(items[i])) {
//删除指定下标元素
removeAt(i);
return true;
}
}
return false;
} finally {
//释放锁
lock.unlock();
}
}
//删除指定下标元素
void removeAt(final int removeIndex) {
final Object[] items = this.items;
// 判断是否在删除元素的下标是否和takeIndex重合
if (removeIndex == takeIndex) {
//清理takeIndex下标中的元素
items[takeIndex] = null;
//移动takeIndex
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// 当和takeIndex不重合,删除的下标在takeIndex~putIndex之间。
// 需要将 [(i+1) - putIndex ] 位置的元素,前移一位,通过覆盖 i 位置的元素,来达到删除的效果。
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
//释放等待notFull等待队列中阻塞的线程
notFull.signal();
}
4 ArrayBlockingQueue使用
4.1 ArrayBlockingQueue API
// 创建一个带有给定的(固定)容量和默认访问策略的 ArrayBlockingQueue。
ArrayBlockingQueue(int capacity)
// 创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue。
ArrayBlockingQueue(int capacity, boolean fair)
// 创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue,它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。
ArrayBlockingQueue(int capacity, boolean fair, Collection extends E> c)
// 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。
boolean add(E e)
// 自动移除此队列中的所有元素。
void clear()
// 如果此队列包含指定的元素,则返回 true。
boolean contains(Object o)
// 移除此队列中所有可用的元素,并将它们添加到给定 collection 中。
int drainTo(Collection super E> c)
// 最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。
int drainTo(Collection super E> c, int maxElements)
// 返回在此队列中的元素上按适当顺序进行迭代的迭代器。
Iterator iterator()
// 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
boolean offer(E e)
// 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
boolean offer(E e, long timeout, TimeUnit unit)
// 获取但不移除此队列的头;如果此队列为空,则返回 null。
E peek()
// 获取并移除此队列的头,如果此队列为空,则返回 null。
E poll()
// 获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
E poll(long timeout, TimeUnit unit)
// 将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。
void put(E e)
// 返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。
int remainingCapacity()
// 从此队列中移除指定元素的单个实例(如果存在)。
boolean remove(Object o)
// 返回此队列中元素的数量。
int size()
// 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
E take()
// 返回一个按适当顺序包含此队列中所有元素的数组。
Object[] toArray()
// 返回一个按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。
T[] toArray(T[] a)
// 返回此 collection 的字符串表示形式。
String toString()
4.2 案例
import java.util.*;
import java.util.concurrent.*;
/*
* ArrayBlockingQueue是“线程安全”的队列,而LinkedList是非线程安全的。
*
* 下面是“多个线程同时操作并且遍历queue”的示例
* (01) 当queue是ArrayBlockingQueue对象时,程序能正常运行。
* (02) 当queue是LinkedList对象时,程序会产生ConcurrentModificationException异常。
*
*/
public class ArrayBlockingQueueDemo1{
// TODO: queue是LinkedList对象时,程序会出错。
//private static Queue queue = new LinkedList();
private static Queue queue = new ArrayBlockingQueue(20);
public static void main(String[] args) {
// 同时启动两个线程对queue进行操作!
new MyThread("ta").start();
new MyThread("tb").start();
}
private static void printAll() {
String value;
Iterator iter = queue.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
System.out.println();
}
private static class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 6) {
// “线程名” + "-" + "序号"
String val = Thread.currentThread().getName()+i;
queue.add(val);
// 通过“Iterator”遍历queue。
printAll();
}
}
}
}