BlockingQueue
在分析BlockingQueue之前,先看一下JDK1.8中对于BlockingQueue的注释说明:
A {@link java.util.Queue} that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.
即获取元素的时候等待队列变为非空,以及存储元素的时候等待队列变为可用。
BlockingQueue有这几个特点:不接受null元素、可能是容量有限的、主要用作生产者-消费者队列、线程安全等。
BlockingQueue同时提供阻塞和非阻塞的方法,例如:
boolean offer(E e); boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
前者立即返回插入结果,后者可以在超时时间之内等待队列可用的时候再执行插入操作。
在JDK1.8中提供了BlockingQueue的七种实现以及一种ScheduledThreadPoolExecutor的内部实现:
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的无界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
接下来将一个个分析每一种队列。
ArrayBlockingQueue
ArrayBlockingQueue是基于数组的队列,构造函数初始化的时候生成定长数组,基于FIFO的原则对数组元素进行操作。
ArrayBlockingQueue内部的阻塞队列是通过重入锁ReenterLock和Condition条件队列实现的,所以ArrayBlockingQueue中的元素存在公平访问与非公平访问的区别,对于公平访问队列,被阻塞的线程可以按照阻塞的先后顺序访问队列,即先阻塞的线程先访问队列。而非公平队列,当队列可用时,阻塞的线程将进入争夺访问资源的竞争中,也就是说谁先抢到谁就执行,没有固定的先后顺序。
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { private static final long serialVersionUID = -817911632652898426L; /** The queued items */ 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; /** Main lock guarding all access 锁,读写共用一个锁*/ final ReentrantLock lock; /** Condition for waiting takes 出列条件 */ private final Condition notEmpty; /** Condition for waiting puts 入列条件 */ private final Condition notFull; /** * Shared state for currently active iterators, or null if there * are known not to be any. Allows queue operations to update * iterator state. */ transient Itrs itrs = null; }
ArrayBlockingQueue的入列操作:
add(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException
offer(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false
offer(E e, long timeout, TimeUnit unit) :将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间,超时返回 false
put(E e) :将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间
四个方法都是通过调用enqueue(E e)方法将元素插入到数组的:
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; count++; notEmpty.signal(); }
enqueue方法就是在putIndex位置添加元素,如果putIndex+1位于队尾则移动到队伍头部,并且通知阻塞在等待出列状态的线程。
ArrayBlockingQueue的出列操作:
poll() :获取并移除此队列的头,如果此队列为空,则返回 null
poll(long timeout, TimeUnit unit) :获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)
take() :获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)
remove(Object o) :从此队列中移除指定元素的单个实例(如果存在)
前三个方法使用dequeue()取出队列头部元素:
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; }
dequeue方法就是在takeIndex的位置取出元素(元素置空),takeIndex的位置后移一位,同时维护迭代期 itrs 对象的信息,最后通知阻塞在等待入列状态的线程。
remove方法,先遍历找到元素,如果找到则调用私有方法removeAt(final int removeIndex),方法从removeIndex到putIndex,一个个前移元素,最后通知阻塞在等待入列的线程。
LinkedBlockingQueue
LinkedBlockingQueue是一个由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE,所以我们在使用LinkedBlockingQueue时建议手动传值,为其提供我们所需的大小,避免队列过大造成机器负载或者内存爆满等情况。其构造函数如下:
//默认大小为Integer.MAX_VALUE public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } //创建指定大小为capacity的阻塞队列 public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); } //创建大小默认值为Integer.MAX_VALUE的阻塞队列并添加c中的元素到阻塞队列 public LinkedBlockingQueue(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // Never contended, but necessary for visibility try { int n = 0; for (E e : c) { if (e == null) throw new NullPointerException(); if (n == capacity) throw new IllegalStateException("Queue full"); enqueue(new Node<E>(e)); ++n; } count.set(n); } finally { putLock.unlock(); } }