前言
在上一篇文章中,我们将阻塞队列各个方法进行了总结,那么阻塞队列到底是怎么实现的呢,今天我们就一起研究下阻塞队列的实现类ArrayBlockingQueue。
1.1简介
在进行阅读源码之前,我们先来看看API对其的描述
A bounded {@linkplain BlockingQueue blocking queue} backed by an array. //ArrayBlockingQueue是由数组支持的有界阻塞阻塞队列 This queue orders elements FIFO (first-in-first-out). // 此队列按照先进先出原则对元素进行排序 The <em>head</em> of the queue is that element that has been on the queue the longest time. //队列的头部是在队列中存在时间最长 The <em>tail</em> of the queue is that element that has been on the queue the shortest time. //队列的尾部实在队列中存在时间最短 New elements are inserted at the tail of the queue, //新的元素插入到队列尾部 and the queue retrieval operations obtain elements at the head of the queue.//队列检索操作从队列的头部开始
This is a classic "bounded buffer"//这是一个典型的有界缓存区 in which a fixed-sized array holds elements inserted by producers and extracted by consumers. // 固定大小的数组包含由生产者插入的元素,并由消费者提取。 Once created, the capacity cannot be changed. //底层数组一旦创建,其容量就不能改变 Attempts to {@code put} an element into a full queue will result in the operation blocking; //当队列满时,put操作会使线程阻塞 attempts to {@code take} an element from an empty queue will similarly block//当队列为空时,take操作也会使线程阻塞
This class supports an optional fairness policy for ordering waiting producer and consumer threads. //该类别支持用于对等待的生产者和消费者线程进行排序的可选的公平性政策。 By default, this ordering is not guaranteed. //默认使用非公平锁 However, a queue constructed with fairness set to {@code true} grants threads access in FIFO order. //通过构造函数公平性 (fairness) 设置为 true来设置为公平锁 Fairness generally decreases throughput but reduces variability and avoids starvation.//公平锁降低吞吐率,但 避免线程饥饿
1.2源码分析
- 继承关系
ArrayBlockingQueue<E> extends AbstractQueue<E>
- 实现接口
implements BlockingQueue<E>, java.io.Serializable
- 重要变量
//存入元素的数组 final Object[] items; //读取元素时数组的下标 int takeIndex; //添加元素时数组的下标 int putIndex; //队列中元素的个数 int count; //可重入锁 final ReentrantLock lock; //等待出队的条件 private final Condition notEmpty; //等待入队的条件 private final Condition notFull;
- 构造函数
//创建一个队列,设定初始容量,默认为非公平锁 public ArrayBlockingQueue(int capacity) { this(capacity, false); } //创建一个队列,设定初始容量,fair设定是公平锁还是非公平锁 public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException();//容量小于等于0,抛出异常 this.items = new Object[capacity];//初始化数组items,数组满则阻塞,不会扩容 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 { 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();//释放锁 } }
- 入队方法
add(E e)方法,源码如下
//add就是调用的offer方法,当offer返回false时,就抛出一个异常,否则返回true
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
可以看出add内部实际上获取的offer方法,当队列满时,抛出一个异常。
offer(E e)方法,源码如下,可以看出该方法将元素添加到队列末尾,成功则返回true;如果队列已满,则插入失败,返回false。
public boolean offer(E e) { checkNotNull(e);//检查元素不能为空 final ReentrantLock lock = this.lock;//可重入锁 lock.lock();//上锁 try { if (count == items.length)//队列已满,返回false return false; else { enqueue(e);//入队 return true; } } finally { lock.unlock();//释放锁 } }
//入队操作 private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x;//放入元素 if (++putIndex == items.length)//放入后存元素的索引等于数组长度 putIndex = 0;//重置存索引为0 count++; notEmpty.signal();//通知因为空而阻塞的线程 }
从上述代码可以看出ArrayBlockingQueue是一个循环队列,在入队之后,由于新加了一个元素,需要通知因为空而阻塞的线程,所以需要调用notEmpty的signal方法。
put(E e)方法将元素添加到队列末尾,如果队列已满,队列会一直阻塞生产者线程。
// public void put(E e) throws InterruptedException { checkNotNull(e);//元素不能为空 final ReentrantLock lock = this.lock; lock.lockInterruptibly();//如果当前线程未被中断,则获得锁 try { while (count == items.length)//队列已满 notFull.await();//当前线程等待,需要其他线程调用notFull.signal()唤醒
enqueue(e);//入队 } finally { lock.unlock();//释放锁 } }
- 出队方法
poll()方法删除队首元素,若成功则返回则返回队首元素,若队列为空,则返回null。
public E poll() { final ReentrantLock lock = this.lock;//可重入锁 lock.lock();//上锁 try { return (count == 0) ? null : dequeue();//若元素个数为0则返回null,否则,调用dequeue,出队列 } finally { lock.unlock();//释放锁 } }
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;//将元素置为null if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); notFull.signal();//通知因队列满而阻塞的线程 return x; }
take()方法从队首取元素,若队列为空,队列会一直阻塞消费者线程。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//如果队列为空,阻塞,直到其他线程调用notEmpty.signal()方法激活该线程
return dequeue();
} finally {
lock.unlock();
}
}
1.3总结
假设A线程调用put方法,如果队列已满,则调用notFull.await()方法,阻塞A线程,唤醒B线程取走一个数据,然后B线程调用notFull.signal() ,这时A线程有可能被唤醒,调用了enqueue()进行入队操作。 假设C线程调用take方法,如果队列为空 ,则调用了notEmpty.await()方法,阻塞C线程,唤醒D线程插入一个元素,D线程调用notEmpty.signal(),这是C线程有可能被唤醒,调用dequeue()方法取走一个元素,这基本就是ArrayBlockingQueue的阻塞实现原理。
ArrayBlockingQueue内部只有一把锁,意味着同一时刻只有一个线程能进行入队或者出队的操作。