目录
1.概述
2.使用案例
3.源码分析
3.1 重要属性
3.2 构造方法
3.3 私有方法入队与出队
(1)入队
(2)出队
3.4 put和take方法
(1)put
(2)take
3.5 offer和poll
(1)offer
(2)poll
3.6 peek
3.7 remainingCapacity
3.8 remove
4.总结
特点
当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:
private static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(10,true);
使用案例见上一篇博客:https://blog.csdn.net/qq_34805255/article/details/101924873
public class ArrayBlockingQueue {
/**
* ArrayBlockingQueue的底层实现为此数组
*
* 并且可以看到此数组被final修饰,数组一旦创建,容量不可变(数组本身的性质),并且,数组引用指向的数组对象不可变
*/
final Object[] items;
/**
* 队首索引位置
*/
int takeIndex;
/**
* 队尾索引位置
*/
int putIndex;
/**
* 队列中元素的个数
*/
int count;
/**
* 采用ReentrantLock来保证线程安全
*/
final ReentrantLock lock;
/**
* 为了保证消费(take)数据的时候如果为空的时候,进行阻塞(等待),使用了Condition
*
* 当获取数据的消费者线程被阻塞时会将该线程放置到notEmpty等待队列中(notEmpty可以理解为等待队列不空的等待队列)
*/
private final Condition notEmpty;
/**
* 为了保证生产(put)数据的时候如果队列满的时候,进行阻塞(等待),使用了Condition
*
* 当插入数据的生产者线程被阻塞时,会将该线程放置到notFull等待队列中(notFull可以理解为等待队列不满的等待队列)。
*/
private final Condition notFull;
}
/**
* 我们在创建一个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);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
/**
* 让元素入队
*/
private void enqueue(E x) {
final Object[] items = this.items;
//插入数据到队尾
items[putIndex] = x;
//如果队尾刚好是数组的最后一个位置,那么,插入后队尾位置变成第一个位置
if (++putIndex == items.length)
putIndex = 0;
//插入后,元素数量加1
count++;
//通知被阻塞的消费者线程
notEmpty.signal();
}
/**
* 让元素出队
*/
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//先用临时变量保存队首元素
E x = (E) items[takeIndex];
//然后将队首引用置空
items[takeIndex] = null;
////如果队首刚好是数组的最后一个位置,那么,删除元素后队首位置变成第一个位置
if (++takeIndex == items.length)
takeIndex = 0;
//队首元素出队后,元素数量减1
count--;
if (itrs != null)
itrs.elementDequeued();
//通知被阻塞的生产者线程
notFull.signal();
return x;
}
dequeue方法也主要做了两件事情:
(E) items[takeIndex]
),删除队首元素的实现是通过让原来队首引用指向null,然后让该引用原来指向的堆中的元素被GC回收 /**
* 阻塞插入:
* 即插入时,在队列满的时候阻塞,在队列不满的时候直接插入
*/
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();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//当队列空的时候,阻塞
while (count == 0)
notEmpty.await();
//当队列不空的时候,直接返回队首元素
return dequeue();
} finally {
lock.unlock();
}
}
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//与put不同的是加的是lock
lock.lock();
try {
//队列满的时候,直接返回false,表示插入不成功
if (count == items.length)
return false;
//队列不满的时候,入队插入元素,返回true,表示插入成功
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public E poll() {
final ReentrantLock lock = this.lock;
//使用lock保证线程安全
lock.lock();
try {
//队列为空,返回null,否则出队队首元素,并返回
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
public E peek() {
final ReentrantLock lock = this.lock;
//通过lock保证线程安全
lock.lock();
try {
//当队列为空的时候返回null,否则返回队尾元素
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
/**
* 当队列为空的时候,该索引位置的值为null,所以会返回null
*/
final E itemAt(int i) {
return (E) items[i];
}
/**
* 计算剩余容量
*/
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//加锁计算剩余的容量
return items.length - count;
} finally {
lock.unlock();
}
}
/**
* 移除指定的元素
*/
public boolean remove(Object o) {
//对输入元素值做检查
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 {
//如果传入的元素值和该索引位置的该元素相等,则移除该元素返回true
if (o.equals(items[i])) {
removeAt(i);
return true;
}
//在遍历过程中,遍历指针i到最后一个位置时,它的下一个位置迭代为0,否则++就行
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
对于 ArrayBlockingQueue,我们可以在构造的时候指定以下三个参数: