ArrayBlockingQueue属于BlockingQueue分支下的一个子类,他是一个阻塞队列,通过名字可知,它底层是采用的数组结构来存放数据,既然是数组,那么长度就是固定的,且它是一个阻塞队列,那么就会涉及到一些锁相关的内容,我们从具体的方法中去了解具体的实现。
1、重要属性
// 数组对象,也就是存放数据的地方
final Object[] items;
// 下一个获取/删除/查看的元素位置
int takeIndex;
// 下一个添加元素的位置
int putIndex;
// 队列中的元素个数
int count;
// 阻塞队列锁(基于Aqs实现,在多线程与高并发专题有介绍)
final ReentrantLock lock;
// 读线程排队队列
private final Condition notEmpty;
// 写线程排队队列
private final Condition notFull;
transient Itrs itrs = null;
2、构造器
/**
* 带参构造
* capacity 容量,代表队列可存放的元素个数,其实就是底层数组的长度items
*/
public ArrayBlockingQueue(int capacity) {
// 调用自己的构造方法 false代表默认使用非公平锁
this(capacity, false);
}
/**
* 带参构造
* capacity 容量,代表队列可存放的元素个数,其实就是底层数组的长度items
* fair true-公平锁 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();
}
/**
* 带参构造
* capacity 容量,代表队列可存放的元素个数,其实就是底层数组的长度items
* fair true-公平锁 false-非公平锁
* c 需要加入到队列的集合
*/
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 {
// 下面的for循环就是将集合中的元素放入数组
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
// 这个数组,模仿了一个循环的过程,比如数组长度为3,当放满了之后,putIndex又会从0开始放,
// 当然是要数据被消费之后
putIndex = (i == capacity) ? 0 : i;
} finally {
// 释放锁
lock.unlock();
}
}
3、主要方法
3.1、add(e) 往队列中添加元素
public boolean add(E e) {
// 最终调用的是offer方法
return super.add(e);
}
public boolean offer(E e) {
checkNotNull(e);
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果count(元素个数)已经等于数组长度,代表没有空间,直接返回false,
// 这里判断就可以限制住putIndex的覆盖(当putIndex走完一圈回到了0,
// 但是数组中的元素没有被消费,就不能继续放元素)
if (count == items.length)
return false;
else {
// 还有空间则进入enqueue方法
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
// 将x放在putIndex位置
items[putIndex] = x;
// 对putIndex进行++操作,如果putIndex到了数组长度,则回到0,从头开始添加
if (++putIndex == items.length)
putIndex = 0;
// 元素个数加1
count++;
// 唤醒在队列中等待的读线程(消费者)
notEmpty.signal();
}
3.2、offer(e)
王队列中放元素的方法,在上面的add中已经介绍过,如果队列已满,则直接返回false,否则就抢锁,成功后往队列添加元素
3.3、put(e)
public void put(E e) throws InterruptedException {
checkNotNull(e);
// 加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 判断队列是否满了,如果满了,那么线程则进入notFull排队等待,
// 那么这里有个问题,为什么要用while,用if进行判断行不行,答案是不行,
// 会产生虚假唤醒的问题,比如,此时C(读线程),A(写线程),B(写线程),在队列中排队,C线程完成工作后,队列
// 有了空间,他就将notFull中等待的写线程进行唤醒,假设唤醒的是A线程,此时A线程并不是直接获得锁,而是从等待
// 队列进入了AQS队列,有了抢锁的资格,但是此时E(写线程)获得锁进来了,发现有空间,将数据放进去了,当E线程
// 是否锁资源后A线程获得了锁,如果不进行长度判断,直接进行写,则会将未被消费的数据覆盖
while (count == items.length)
// 没有空间则挂起,等待唤醒
notFull.await();
enqueue(e);
} finally {
// 释放锁
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
// 将x放在putIndex位置
items[putIndex] = x;
// 对putIndex进行++操作,如果putIndex到了数组长度,则回到0,从头开始添加
if (++putIndex == items.length)
putIndex = 0;
// 元素个数加1
count++;
// 唤醒在队列中等待的读线程(消费者)
notEmpty.signal();
}
3.3、offer(e,time,unit)
此方法在加入元素时,如果没有空间,会等待timeout(时间),过后才返回成功或者失败,等待过程中被中断,则抛出中断异常
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 {
// 判断队列是否满了,如果满了,那么线程则进入notFull排队等待,此次的while与put同理
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;
// 将x放在putIndex位置
items[putIndex] = x;
// 对putIndex进行++操作,如果putIndex到了数组长度,则回到0,从头开始添加
if (++putIndex == items.length)
putIndex = 0;
// 元素个数加1
count++;
// 唤醒在队列中等待的读线程(消费者)
notEmpty.signal();
}
3.4、remove(Object o)
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 {
// 如果o与i位置的元素匹配上
if (o.equals(items[i])) {
removeAt(i);
return true;
}
// 没匹配上,则继续找,找到数组尾巴了,就转圈回去从头找
if (++i == items.length)
i = 0;
} while (i != putIndex);
// i如果等于了putIndex,说明所有元素循环结束,没有找到o
}
return false;
} finally {
lock.unlock();
}
}
void removeAt(final int removeIndex) {
final Object[] items = this.items;
// 如果要删除的元素正好是下一个要获取的元素,直接修改takeIndex就完成了
if (removeIndex == takeIndex) {
// 将takeIndex位置的元素置空
items[takeIndex] = null;
// 那么下一个要获取的元素位置也相应的要变化
if (++takeIndex == items.length)
takeIndex = 0;
// 元素个数减1
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// 否则的话,则要对数组元素进行移动
final int putIndex = this.putIndex;
// for循环则是在移动元素
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;
}
}
// 元素个数减1
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
// 删除元素后,队列就有了空间,唤醒等待的写线程
notFull.signal();
}
3.5、poll()
获取并删除队列头元素,队列为空则返回null
public E poll() {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 队列为空则返回null,否则调用dequeue
return (count == 0) ? null : dequeue();
} finally {
// 释放锁
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
// 取出takeIndex位置的元素
E x = (E) items[takeIndex];
// 将该位置置空
items[takeIndex] = null;
// 重置takeIndex的值
if (++takeIndex == items.length)
takeIndex = 0;
// 元素个数减1
count--;
if (itrs != null)
itrs.elementDequeued();
// 唤醒等待的写线程
notFull.signal();
// 返回元素
return x;
}
3.6、take()
此方法获取不到元素,则一直死等
public E take() throws InterruptedException {
// 加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 没有元素 此处的while与上面的put的while有异曲同工之妙
while (count == 0)
// 进入等待队列挂起
notEmpty.await();
// 队列不为空 则进行出队操作
return dequeue();
} finally {
// 释放锁
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
// 取出takeIndex位置的元素
E x = (E) items[takeIndex];
// 将该位置置空
items[takeIndex] = null;
// 重置takeIndex的值
if (++takeIndex == items.length)
takeIndex = 0;
// 元素个数减1
count--;
if (itrs != null)
itrs.elementDequeued();
// 唤醒等待的写线程
notFull.signal();
// 返回元素
return x;
}
3.7、poll(long timeout, TimeUnit unit)
此方法,若是拿不到元素,则等待一段时间,时间一到还拿不到则放弃
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();
}
}
private E dequeue() {
final Object[] items = this.items;
// 取出takeIndex位置的元素
E x = (E) items[takeIndex];
// 将该位置置空
items[takeIndex] = null;
// 重置takeIndex的值
if (++takeIndex == items.length)
takeIndex = 0;
// 元素个数减1
count--;
if (itrs != null)
itrs.elementDequeued();
// 唤醒等待的写线程
notFull.signal();
// 返回元素
return x;
}
3.8、peek()
此方法查看队头元素,但不删除,队列为空则返回空
public E peek() {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 返回takeIndex位置的元素
return itemAt(takeIndex);
} finally {
// 释放锁
lock.unlock();
}
}
final E itemAt(int i) {
return (E) items[i];
}