继承结构
可以看到ArrayBlockingQueue实现BlockingQueue,因此需要具备阻塞的功能。一般在多前程环境下进行使用,可以很方便的实现生产者消费者模式。
重要属性
// 使用数组存储元素
final Object[] items;
// 取元素的指针
int takeIndex;
// 放元素的指针
int putIndex;
// 元素数量
int count;
// 重入锁
final ReentrantLock lock;
// 重入锁引出的非空条件
private final Condition notEmpty;
// 重入锁引出的非满条件
private final Condition notFull;
可以看到,ArrayBlockingQueue底层使用数组存储,使用两个指针粉笔诶指向取数据与存数据位置。使用ReentrantLock进行加锁操作。
构造函数
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();
}
// 在初始化的时候用集合进行填充,并把放入指针指向队列的末尾
// 当c.size() > capacity的时候,将会抛出 IllegalArgumentException异常
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 {
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线程初始化对象,由于指令乱排的影响可能会导致对象暴露但是实际上数组并没有初始化,那么就会导致线程安全问题
- 防止可见性问题,数组自身不局部防止可见性的能力,有可能A线程初始化对象之后,数组对象还在当前线程的内存中,其他线程感知不到,那么就有可能导致线程安全问题
重要方法
入队方法
入队有四个方法,分别是
- add(E e) 调用offer(e)如果成功返回true,如果失败抛出异常
- offer(E e) 返回true/false
- put(E e) 不断循环调用入队方法,直到成功
- offer(E e, long timeout, TimeUnit unit) 不断循环调用入队方法,unit时间后放弃
public boolean add(E e) {
// 调用父类的add(e)方法
return super.add(e);
}
// super.add(e)
public boolean add(E e) {
// 调用offer(e)如果成功返回true,如果失败抛出异常
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 {
if (count == items.length)
// 如果数组满了就返回false
return false;
else {
// 如果数组没满就调用入队方法并返回true
enqueue(e);
return true;
}
} finally {
// 解锁
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 加锁,如果线程中断了抛出异常
lock.lockInterruptibly();
try {
// 如果数组满了,使用notFull等待,如果被唤醒将会再次查看是否能进行
while (count == items.length)
notFull.await();
// 入队
enqueue(e);
} 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 {
// 如果数组满了,就阻塞nanos纳秒
// 如果唤醒这个线程时依然没有空间且时间到了就返回false
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;
// 把元素直接放在放指针的位置上
items[putIndex] = x;
// 如果放指针到数组尽头了,就返回头部
if (++putIndex == items.length)
putIndex = 0;
// 数量加1
count++;
// 唤醒notEmpty,因为入队了一个元素,所以肯定不为空了
notEmpty.signal();
}
出队方法
出队也有4个方法:
#poll()
方法:获取并移除此队列的头,如果此队列为空,则返回null
。#poll(long timeout, TimeUnit unit)
方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。#take()
方法:获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。#remove(Object o)
方法:从此队列中移除指定元素的单个实例(如果存在)。
public E remove() {
// 调用poll()方法出队
E x = poll();
if (x != null)
// 如果有元素出队就返回这个元素
return x;
else
// 如果没有元素出队就抛出异常
throw new NoSuchElementException();
}
public E poll() {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 如果队列没有元素则返回null,否则出队
return (count == 0) ? null : 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();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 加锁
lock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞等待nanos纳秒
// 如果下一次这个线程获得了锁但队列依然无元素且已超时就返回null
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
// 取取指针位置的元素
E x = (E) items[takeIndex];
// 把取指针位置设为null
items[takeIndex] = null;
// 取指针前移,如果数组到头了就返回数组前端循环利用
if (++takeIndex == items.length)
takeIndex = 0;
// 元素数量减1
count--;
if (itrs != null)
itrs.elementDequeued();
// 唤醒notFull条件
notFull.signal();
return x;
}
转移方法
转移方法帮助我们将队列中的数据放到我们指定的集合中
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 取入参与真实值最小作为真正的转移量
int n = Math.min(maxElements, count);
int take = takeIndex;
int i = 0;
try {
// 从take开始遍历放入集合
while (i < n) {
@SuppressWarnings("unchecked")
E x = (E) items[take];
c.add(x);
items[take] = null;
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// Restore invariants even if c.add() threw
if (i > 0) {
count -= i;
takeIndex = take;
if (itrs != null) {
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
itrs.takeIndexWrapped();
}
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
}
总结
ArrayBlockingQueue是有界阻塞队列,底层使用数组进行存储,分配两个指针分别指向取与存的位置,逻辑上数组会被重复使用是个环形数组。内部使用ReetrantLock来控制并发,使用两个condition分别控制非满与非空条件。