ArrayBlockingQueue源码分析
基于数组实现队列,使用时必须指定队列长度
成员变量
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();
}
//指定数组长度,公平锁/非公平锁,存入元素
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 {
final Object[] items = this.items;
int i = 0;
try {
for (E e : c)
items[i++] = Objects.requireNonNull(e);
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
添加元素方法
add(E e)方法
调用了AbstractQueue中的add方法,调用了offer方法,如果添加失败抛出异常,队列已满。
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
offer(E e)方法
添加元素成功返回true,否则返回false
public boolean offer(E e) {
//校验数据不能为null
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
//加锁添加数据,线程安全
lock.lock();
try {
//如果队列满了,返回false
if (count == items.length)
return false;
else { //否则,添加成功
//添加数据
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E e) {
final Object[] items = this.items;
//存储数据
items[putIndex] = e;
//将要取数据的数组下标+1,如果等于长度了,将putIndex置为0。为了满足FIFO,数组满了后下次添加元素应该从0添加元素,0位置的元素一定先被取出,等被取出再添加新元素
if (++putIndex == items.length) putIndex = 0;
count++;
//唤醒消费者线程
notEmpty.signal();
}
offer(E e, long timeout, TimeUnit unit)方法
指定时间阻塞,如果指定时间内没有添加元素成功,返回false
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//校验元素是否为null
Objects.requireNonNull(e);
//时间单位转换为纳秒
long nanos = unit.toNanos(timeout);
//加锁
final ReentrantLock lock = this.lock;
//允许中断线程抛出异常
lock.lockInterruptibly();
try {
//如果元素满了需要挂起线程或者时间到了返回false
while (count == items.length) {
if (nanos <= 0L)
return false;
//挂起线程,是否锁资源,返回剩余阻塞时间
nanos = notFull.awaitNanos(nanos);
}
//添加元素
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
put(E e) 方法
一直等,直到存入元素。
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
//阻塞,直到被唤醒然后重新尝试添加元素
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
移除元素方法
remove()方法
移除元素,如果元素为空,抛出移除,调用的poll方法
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
poll()方法
public E poll() {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//如果队列长度为0,返回空,否则取出数据
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
//获取列表
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//根据取数据下标,获取当前下标下元素
E e = (E) items[takeIndex];
//将当前下标下元素置为null
items[takeIndex] = null;
//取数据下标+1,如果+1后长度等于数组长度,将取数据的下标置为0
if (++takeIndex == items.length) takeIndex = 0;
//数组长度-1
count--;
if (itrs != null)
itrs.elementDequeued();
//唤醒生产者线程
notFull.signal();
return e;
}
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) {
if (nanos <= 0L)
//一定时间后仍然没有数据返回null
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
take()方法
如果队列为空,挂起线程直到添加元素成功
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
//挂起线程,等待被唤醒
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
虚假唤醒(代码中的判空或者判断队列是否满了为什么使用while循环)
while (count == 0)
- 假设A、B、C三个线程
- A,B两个线程消费元素,都执行了(count == 0)判断后执行notEmpty.await()方法,挂起线程
- 此时C线程添加完元素,A、B线程被唤醒,无论谁先拿到锁去消费数据都需要重新判断count == 0条件
- 不然会有一个线程在count的情况下消费数据
while (count == items.length)
- 假设A、B、C三个线程
- A、B线程执行了(count == items.length)后执行notFull.await()方法,挂起线程
- 此时C线程刚消费完一个元素,A、B线程被唤醒,无论谁先拿到锁添加数据都需要重新判断count == items.length条件
- 不然A、B会有一个线程在队列满的情况下添加数据,导致数据被覆盖
以上两种情况被成为虚假唤醒,如果不使用while循环会导致数据安全性问题。