ArrayBlockingQueue是使用独占锁(ReentrantLock)实现的阻塞队列,ArrayBlockingQueue与LinkedBlockingQueue的不同之处,就是ArrayBlockingQueue底层的数据结构使用的是数组。且添加数据和获取数据用的都是同一个独占锁,所以读写并没有分离,读写操作不能并行执行。
属性源码分析:
/** 数组用于存储数据元素 */
final Object[] items;
/** 获取元素时数据元素下标 */
int takeIndex;
/** 添加元素时数据元素下标 */
int putIndex;
/** 队列中元素个数 */
int count;
/** 全局锁,添加数据和获取数据都使用该独占锁 */
final ReentrantLock lock;
/** 获取数据时,队空线程阻塞时的条件队列 */
private final Condition notEmpty;
/** 添加数据时,队满线程阻塞时的条件队列 */
private final Condition notFull;
常用方法:
添加数据
boolean offer(E e):从队尾插入数据,如果队满则遗弃数据,返回false,不会造成阻塞。
void put(E e):从队尾插入数据,如果队满,线程阻塞,并把线程放入notFull条件队列
获取数据:
E poll():获取并弹出队头元素,如果队空,则返回null,不会造成阻塞。
E take():获取并弹出队头元素,如果队空,线程阻塞,并不线程放入notEmpty条件队列
E peek():获取队头元素,但不弹出,如果队空,则返回null。
其他方法
int size():获取元素个数
常用方法源码分析:
boolean offer(E e):向队列尾部插入一个数据元素,如果队列有空闲则插入成功返回true,如果队满,则遗弃当前元素返回false。如果插入元素为null,则抛出NullPointerException。
public boolean offer(E e) {
//1)、数据元素为空时,抛出NullPointerException异常
checkNotNull(e);
//2)、获取锁资源
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果队满,遗弃数据元素返回false
if (count == items.length)
return false;
else {
//插入数据元素,返回true
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
//数据元素为空时,抛出NullPointerException异常
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
//入队
private void enqueue(E x) {
//数据元素插入队列
final Object[] items = this.items;
items[putIndex] = x;
//计算下一个元素存放的位置
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal(); //唤醒notEmpty条件队列中的一个线程
}
void put(E e):往队列尾部插入数据元素,如果数据为null,则抛出NullPointerException,如果队列空闲,插入成功并返回,如果队满,则线程阻塞,把线程放入notFull条件队列,如果该线程在其他线程中设置了中断表示,则会抛出InterruptedException。
public void put(E e) throws InterruptedException {
//如果插入元素为null,则抛出NullPointerException
checkNotNull(e);
//获取独占锁lock
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队满,线程阻塞放入notNull条件队列
while (count == items.length)
notFull.await();
//数据元素入队
enqueue(e);
} finally {
lock.unlock();
}
}
E poll():获取并弹出队头元素,如果队空,则返回null,不会造成阻塞。
public E poll() {
//获取独占锁lock
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;
@SuppressWarnings("unchecked")
//获取队头数据元素
E x = (E) items[takeIndex];
//队头数据元素设置为null,弹出队头元素
items[takeIndex] = null;
//队头指针计算,队列元素个数减1
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //唤醒notFull条件队列中的一个线程
return x;
}
E take():获取并弹出队头元素,如果队空,线程阻塞,并不线程放入notEmpty条件队列。如果该线程在其他线程中调用了线程中断表示,则会抛出InterruptedException异常
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//队空,线程阻塞,把线程放入notEmpty条件队列
while (count == 0)
notEmpty.await();
//队列不为空,获取并弹出队头数据元素
return dequeue();
} finally {
lock.unlock();
}
}
E peek():获取队头元素,但不弹出,如果队空,则返回null。 源码就是根据takeIndex指针获取数据元素。
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
int size():获取元素个数。加锁是避免线程间的可见性问题,从主内存中获取数据元素。同时也阻塞了其他线程的入队和出队操作,获取的数据元素是精确的。
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
图解ArrayBlockingQueue
参考:《java并发编程之美》