java实现BlockingQueue接口的队列解析
阻塞队列与非阻塞队列一个最大的区别就是:阻塞队列能够阻塞当前试图从队列中获取元素的线程,而非阻塞队列不会。因此在面对类似消费者-生产者的模型时,使用非阻塞队列就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。
一.jdk中的阻塞队列概况
阻塞队列由BlockingQueue进行定义。在jdk 1.8中实现了该接口的主要有以下几个:
ArrayBlockingQueue –基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的线程最优先能够访问队列。
LinkedBlockingQueue–基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
PriorityBlockingQueue–无界阻塞队列,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。
DelayQueue –基于PriorityQueue实现的延迟队列,是一个无界的阻塞队列,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。因此向队列中插入时永远不会阻塞,获取时才有可能被阻塞。
SynchronousQueue –同步阻塞队列,队列大小为1,一个元素要放到该队列中必须有一个线程在等待获取元素。
DelayedWorkQueue –该队列为ScheduledThreadPoolExecutor中的静态内部类,ScheduledThreadPoolExecutor便是通过该队列使得队列中的元素按一定顺序排列从而时延迟任务和周期性任务得以顺利执行。
BlockingDeque–双向阻塞队列的接口。
TransferQueue–接口,定义了另一种阻塞情况:生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费,而BlockingQueue只需将元素添加到队列中后生产者便会停止被阻塞。
二.阻塞队列与非阻塞队列中的方法对比
非阻塞队列常用方法
在非阻塞队列中常用的操作队列的方法主要是下面几种:
add(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;
remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;
offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;
poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null;
peek():获取队首元素,但不移除。若成功,则返回队首元素;否则返回null
对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。原因看上面的描述很明显了:使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法需要捕获异常才能判断操作是否成功。另外需要注意非阻塞队列的这些方法都没有进行同步处理。
阻塞队列常用方法
阻塞队列也实现了Queue,因此也具有上述方法并且都进行了同步处理。除此之外还有4个很有用的方法:
put(E e):向队尾存入元素,如果队列满,则等待;
take():从队首取元素,如果队列为空,则等待;
offer(E e,long timeout, TimeUnit unit):向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
poll(long timeout, TimeUnit unit):从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取不到,则返回null;否则返回取得的元素;
三.各阻塞队列实现原理
ArrayBlockingQueue
首先看一下类中的成员变量
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/**
* Serialization ID. This class relies on default serialization
* even for the items array, which is default-serialized, even if
* it is empty. Otherwise it could not be declared final, which is
* necessary here.
*/
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;
/*
* 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;
}
可以看到使用数组来保存元素。takeIndex是队首下标,putIndex是队尾下标,count是队列中元素数目。lock是一个可重入锁,notEmpty和notFull是等待条件。关于可重入锁和Condition的相关知识将会在其他博客中阐述。
再来看看它的构造函数
//指定大小的队列
public ArrayBlockingQueue(int capacity) {
}
//指定大小和公平性的队列
public ArrayBlockingQueue(int capacity, boolean fair) {
}
//指定大小和公平性并使用已有的集合构建
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
}
在来看看它的两个关键方法put和take的实现方式。
首先看看put
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();
}
}
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
/**
* Inserts element at current put position, advances, and signals.
* Call only when holding lock.
*/
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();
}
代码摘自jdk1.8,之前的代码和它稍有不同但核心没变。首先获取锁,判断当前队列是否满,满则阻塞,被其他线程唤醒时拆入元素,插入成功后通过notEmpty.signal()唤醒因队列为空等待取元素的线程。最后释放锁。
再来看看take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
/**
* Extracts element at current take position, advances, and signals.
* Call only when holding lock.
*/
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方法获取元素,获取成功后通过notFull.signal();唤醒因队列满无法放元素的线程
LinkerBlockingQueue
LinkerBlockingQueue使用node结构来存储数据
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
put和take方法和ArrayBlockingQueue类似,这里就不阐述了,感兴趣的读者可以自行阅读源码
PriorityBlockingQueue
该队列为无界队列并且会按照元素的优先级对元素进行排序。首先看一下成员变量
/**
* Default array capacity.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Priority queue represented as a balanced binary heap: the two
* children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The
* priority queue is ordered by comparator, or by the elements'
* natural ordering, if comparator is null: For each node n in the
* heap and each descendant d of n, n <= d. The element with the
* lowest value is in queue[0], assuming the queue is nonempty.
*/
private transient Object[] queue;
/**
* The number of elements in the priority queue.
*/
private transient int size;
/**
* The comparator, or null if priority queue uses elements'
* natural ordering.
*/
private transient Comparator<? super E> comparator;
/**
* Lock used for all public operations
*/
private final ReentrantLock lock;
/**
* Condition for blocking when empty
*/
private final Condition notEmpty;
/**
* Spinlock for allocation, acquired via CAS.
*/
private transient volatile int allocationSpinLock;
/**
* A plain PriorityQueue used only for serialization,
* to maintain compatibility with previous versions
* of this class. Non-null only during serialization/deserialization.
*/
private PriorityQueue<E> q;
PriorityBlockingQueue使用数组保存元素,在空间不够时会进行扩容。每个参数的含义上面的英文注释写的很清楚了,为阅读方便这里再用中文简单解释一下:
DEFAULT_INITIAL_CAPACITY - 初始时的数组大小
MAX_ARRAY_SIZE - 数组最大值
queue - 保存元素的数组,其实被实现为了一个最大堆
size - 保存队列中元素个数
comparator - 比较元素优先级使用的Comparator
lock - 同步用的可重入锁
notEmpty - 同步用的Condition
allocationSpinLock - 该int值配合unsafe类实现了一个同步用的自旋锁,后续代码中将能看到
q - 为了兼容老代码在序列化和反序列化时使用的队列,只有在序列化/反序列化时该队列中才有值。可以看到前面用于保存元素的queue被transient修饰,因此序列化时只能通过该对象q
在看看构造函数
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityBlockingQueue(int initialC