阻塞队列之ArrayBlockingQueue源码分析
什么是阻塞队列?阻塞队列就是一个容器,该容器提供了俩个能力。
1 存储和读取数据对象。
2 当存储数据对象时容器已满,就阻塞当前线程,直到有新的数据添加到容器时该线程才继续执行。同理,取数据也是一样。
那么阻塞队列最常用的就是存储数据和获取数据的几个方法。
ArrayBlockingQueue
//可见是以数组存储的数据
final Object[] items;
/** items index for next take, poll, peek or remove */
//取数据的指向
int takeIndex;
//放数据的指向
/** items index for next put, offer, or add */
int putIndex;
//数据的个数
/** Number of elements in the queue */
int count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
// 存放数据
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
//可见ArrayBlockingQueue 当存放的数据超过存储容量时,会从头开始覆盖存储
if (++putIndex == items.length)
putIndex = 0;
count++;
//通知取数据时因为没有数据而阻塞的线程取数据
notEmpty.signal();
}
//取数据
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
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;
}
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and {@code false} if this queue
* is full. This method is generally preferable to method {@link #add},
* which can fail to insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果数组满了,则不插入数据直接返回
if (count == items.length
return false;
else {
//直接将数据放置到数组的末尾
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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();
}
}
/**
* Inserts the specified element at the tail of this queue, waiting
* up to the specified wait time for space to become available if
* the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
//插入一条数据,如果数据满了阻塞等待一段时间,还是满的话直接返回false,不满的话就插入。
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 {
while (count == items.length) {
if (nanos <= 0)
return false;
//如果数据满了,当前阻塞指定的时间后发现还没有被唤醒,则主动唤醒该线程并返回一个小于等于0的值,如果循环后发现nanos<=0,直接返回false.
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
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 {
while (count == 0)
//没有数据的话就阻塞当前线程,直到被唤醒
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
//原理同offer(E e, 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) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
总结一下,ArrayBlockingQueue是基于数组的阻塞队列,按照先进先出的规则存取数据,因为使用的ReentrantLock,我们还可以定义存取的时候是采用公平锁的方式还是不公平锁的方式。