目录
1. BlockingQueue接口
2. BlockingQueue的分类
3. 有界阻塞队列——ArrayBlockingQueue和LinkedBlockingQueue
4. 优先无界阻塞队列——PriorityBlockingQueue
5. 同步阻塞队列——SynchronousQueue
6. 延时阻塞队列——DelayQueue
BlockingQueue是java.util.concurrent包中定义的一个阻塞队列的接口,concurrent包中实现了多种阻塞队列,它们都实现了该接口。有了这些阻塞队列,可以更好的实现多线程之间的数据共享;而如果能灵活运用这些类,便可以在工作中更好的实现高质量的多线程高并发程序。本章首先从BlockingQueue接口开始,详细分析了concurrent包中的这些阻塞队列的实现。
BlockingQueue接口继承了Queue接口,所以其本身也可以作为一般的队列使用。在此基础上,BlockingQueue定义了如下几个阻塞方法:
void put(E e) throws InterruptedException; 该方法为阻塞式入队方法,如果发生中断异常会抛出。
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; 该方法为阻塞式入队方法,该方法可以设置一个超时时间,当发生超时时可返回false表示入队失败。
E take() throws InterruptedException; 该方法为阻塞式出队方法,如果发生中断异常会抛出。
E poll(long timeout, TimeUnit unit) throws InterruptedException; 该方法为阻塞式出对方法,该方法也可以设置一个超时时间,如果发生超时,将会返回null。
concurrent包中定义了多种阻塞队列:
本节将concurrent包中所有的阻塞队列的特点进行了简要的概括,下文将详细分析每种阻塞队列的实现原理与使用场景等。
concurrent包实现了两种有界阻塞队列:ArrayBlockingQueue和LinkedBlockingQueue,顾名思义,ArrayBlockingQueue通过数组实现阻塞队列,LinkedBlockingQueue则通过链表实现阻塞队列。两者的实现方案类似,因此,在此以ArrayBlockingQueue为例,介绍有界阻塞队列的实现原理。
在ArrayBlockingQueue中定义了两个方法用来为阻塞队列插入和删除元素。
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();
}
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;
}
其中,enqueue方法为队列添加元素,dequeue方法为队列删除元素。从这两个方法可以看出,ArrayBlockingQueue中定义了一个items数组用来保存阻塞队列的元素,然后分别定义了putIndex和takeIndex来表示插入和删除元素的位置。当putIndex或takeIndex增长为数组的长度时,会将其置零,表示该队列为一个循环队列。ArrayBlockingQueue中的元素个数则用count来统计。ArrayBlockingQueue可通过构造方法指定阻塞队列的容量capacity。
ArrayBlockingQueue基于ReentrantLock和Condition来实现阻塞队列和线程安全。ArrayBlockingQueue定义了一个ReentrantLock对象lock和两个Condition对象notEmpty和notFull。首先,在调用阻塞队列相关的方法时,通过ReentrantLock为操作加锁;然后进行入队和出队操作。入队时,如果队列已满,对于阻塞类的入队方法会调用notFull的await等待,直到有线程调用出队方法,此时会触发notFull的signal方法通知阻塞的线程入队。反之,如果队列为空,对于阻塞类的出队方法会调用notEmpty的await方法等待,直到有线程调用入队方法,就会触发notEmpty的signal方法通知阻塞的线程出队。而对于非阻塞类的入队和出对方法,入队和出队则直接返回,不会阻塞。
LinkedBlockingQueue是基于链表的有界阻塞队列,因为都是有界阻塞队列,所以其实现与ArrayBlockingQueue类似,在此不再赘述其实现原理。这里仅总结一下ArrayBlockingQueue和LinkedBlockingQueue的异同点,以便在平时应用中选择更加合适的队列。
ArrayBlockingQueue和LinkedBlockingQueue都是有界阻塞队列,也就是说队列的容量有限制,都是通过构造函数来指定队列的容量。而其不同点主要体现在以下几个方面:
PriorityBlockingQueue是一个可以指定优先级的无界阻塞队列,其实现原理是在该队列中添加了一个Comparator比较器用于排序。
首先,PriorityBlockingQueue是一个无界阻塞队列,因此其在创建时指定的容量只是一个初始容量,随着元素的不断增多,其会调用tryGrow方法进行扩容。
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
int minCap = oldCap + 1;
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
newCap = MAX_ARRAY_SIZE;
}
if (newCap > oldCap && queue == array)
newArray = new Object[newCap];
} finally {
allocationSpinLock = 0;
}
}
if (newArray == null) // back off if another thread is allocating
Thread.yield();
lock.lock();
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
扩容时,首先会释放锁,然后计算新数组的容量,然后再为主线程加锁的方式进行。其中,容量过大可能会溢出,抛出OOM异常。
介绍了扩容之后,接着对队列的入队和出队方法进行介绍。PriorityBlockingQueue使用堆排序算法对元素进行排序。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator super E> cmp = comparator;
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
入队时,如果元素的数量超过了当前的容量,则首先进行扩容操作;然后,会根据比较器进行插入操作。其会调用队列中的siftUpComparable或siftUpUsingComparator方法插入数据,具体根据创建PriorityBlockingQueue对象时是否传入比较器决定。
private static void siftUpComparable(int k, T x, Object[] array) {
Comparable super T> key = (Comparable super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = array[parent];
if (key.compareTo((T) e) >= 0)
break;
array[k] = e;
k = parent;
}
array[k] = key;
}
private static void siftUpUsingComparator(int k, T x, Object[] array,
Comparator super T> cmp) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = array[parent];
if (cmp.compare(x, (T) e) >= 0)
break;
array[k] = e;
k = parent;
}
array[k] = x;
}
由代码可知,PriorityBlockingQueue采用堆排序保持插入元素的顺序,并以此来保证优先级。因为其是一个无界队列,因此元素入队时是不会产生阻塞的。
元素出队则根据排序的元素顺序进行出队,主要调用了dequeue方法出队。
private E dequeue() {
int n = size - 1;
if (n < 0)
return null;
else {
Object[] array = queue;
E result = (E) array[0];
E x = (E) array[n];
array[n] = null;
Comparator super E> cmp = comparator;
if (cmp == null)
siftDownComparable(0, x, array, n);
else
siftDownUsingComparator(0, x, array, n, cmp);
size = n;
return result;
}
}
该方法会取数组的第一个元素出队,然后调用对应的siftDownComparable或siftDownUsingComparator方法保持堆的数据结构。
private static void siftDownComparable(int k, T x, Object[] array,
int n) {
if (n > 0) {
Comparable super T> key = (Comparable super T>)x;
int half = n >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = array[child];
int right = child + 1;
if (right < n &&
((Comparable super T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
if (key.compareTo((T) c) <= 0)
break;
array[k] = c;
k = child;
}
array[k] = key;
}
}
private static void siftDownUsingComparator(int k, T x, Object[] array,
int n,
Comparator super T> cmp) {
if (n > 0) {
int half = n >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = array[child];
int right = child + 1;
if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
c = array[child = right];
if (cmp.compare(x, (T) c) <= 0)
break;
array[k] = c;
k = child;
}
array[k] = x;
}
}
SynchronousQueue是一个同步阻塞队列,其本身并不保存元素。每个入队操作都必须等待一个出队操作才能继续下一个入队操作。
SynchronousQueue队列中实现了两个Transferer接口:TransferStack和TransferQueue,分别用来实现非公平策略和公平策略,这两种类型的底层则都是通过链表来实现的。Transferer接口是一个为双堆栈和队列提供的共享内部API,及在SynchronousQueue中,入队和出对方法都会调用Transferer接口的transfer方法。
/**
* Shared internal API for dual stacks and queues.
*/
abstract static class Transferer {
/**
* Performs a put or take.
*
* @param e if non-null, the item to be handed to a consumer;
* if null, requests that transfer return an item
* offered by producer.
* @param timed if this operation should timeout
* @param nanos the timeout, in nanoseconds
* @return if non-null, the item provided or received; if null,
* the operation failed due to timeout or interrupt --
* the caller can distinguish which of these occurred
* by checking Thread.interrupted.
*/
abstract E transfer(E e, boolean timed, long nanos);
}
下面以TransferStack为例介绍SynchronousQueue队列的实现原理。
/**
* Puts or takes an item.
*/
@SuppressWarnings("unchecked")
E transfer(E e, boolean timed, long nanos) {
SNode s = null; // constructed/reused as needed
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SNode h = head;
if (h == null || h.mode == mode) { // empty or same-mode
if (timed && nanos <= 0) { // can't wait
if (h != null && h.isCancelled())
casHead(h, h.next); // pop cancelled node
else
return null;
} else if (casHead(h, s = snode(s, e, h, mode))) {
SNode m = awaitFulfill(s, timed, nanos);
if (m == s) { // wait was cancelled
clean(s);
return null;
}
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
return (E) ((mode == REQUEST) ? m.item : s.item);
}
} else if (!isFulfilling(h.mode)) { // try to fulfill
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) { // loop until matched or waiters disappear
SNode m = s.next; // m is s's match
if (m == null) { // all waiters are gone
casHead(s, null); // pop fulfill node
s = null; // use new node next time
break; // restart main loop
}
SNode mn = m.next;
if (m.tryMatch(s)) {
casHead(s, mn); // pop both s and m
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // lost match
s.casNext(m, mn); // help unlink
}
}
} else { // help a fulfiller
SNode m = h.next; // m is h's match
if (m == null) // waiter is gone
casHead(h, null); // pop fulfilling node
else {
SNode mn = m.next;
if (m.tryMatch(h)) // help match
casHead(h, mn); // pop both h and m
else // lost match
h.casNext(m, mn); // help unlink
}
}
}
}
TransferStack和TransferQueue都是Transfer接口的实现类,这两个类都通过实现transfer()方法达到同步队列的目的。对于TransferStack类,栈中的每个节点包含三种模式:
transfer根据传入的参数判断当前调用方法是什么模式。根据这三种模式,transfer方法的处理方式也分为以下三种:
TransferQueue与TransferStack实现类似,只是底层的数据结构为队列。在此不再进行分析,如果感兴趣可以自行阅读源码。
介绍了Transfer接口与实现,接下来便是 SynchronousQueue的各个方法如何通过Transfer接口实现同步队列。其实SynchronousQueue的方法实现非常简单,不管是入队还是出队,都是调用Transfer接口的transfer方法来完成的。对于入队方法,transfer的第一个参数不为空;而对于出队方法,transfer的第一个参数为空。
首先,要使用延时队列,存放的元素必须实现Delayed接口。该接口的定义如下:
public interface Delayed extends Comparable {
/**
* Returns the remaining delay associated with this object, in the
* given time unit.
*
* @param unit the time unit
* @return the remaining delay; zero or negative values indicate
* that the delay has already elapsed
*/
long getDelay(TimeUnit unit);
}
该接口只有一个方法getDeplay,该方法返回给定单位的与该对象关联的剩余延迟时间。
DeplayQueue队列通过PriorityQueue来实现延时队列,即DeplayQueue会根据元素的延迟时间来排序。
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
入队时,通过调用PriorityQueue的offer()方法入队,保持队列元素的排列顺序;然后调用peek()方法获取优先队列的队首元素,该元素为延迟时间最小的元素,即即将过期,此时会调用available的signal()方法通知等待的线程有元素可用;然后返回结果。实现较为简单。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null) {
if (nanos <= 0)
return null;
else
nanos = available.awaitNanos(nanos);
} else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return q.poll();
if (nanos <= 0)
return null;
first = null; // don't retain ref while waiting
if (nanos < delay || leader != null)
nanos = available.awaitNanos(nanos);
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
long timeLeft = available.awaitNanos(delay);
nanos -= delay - timeLeft;
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}
出队时,不管是非阻塞式方法还是阻塞式方法,即实现都一样。首先,通过peek()方法获取队首元素,如果该元素为空或者该元素的getDelay()方法返回值大于0,即还未到期,则返回空或等待;反之,即该元素的getDelay()方法返回值小于0,则直接调用poll()方法返回该值。
这里,就介绍了大部分的阻塞队列。而根据第二节中所介绍的,还有LinkedTransferQueue和LinkedBlockingDeque两个队列的实现原理没有介绍。对于LinkedBlockingDeque,其与LinkedBlockingQueue类似,不过LinkedBlockingDeque是一个双向队列,所以在此不再多进行介绍。而LinkedTransferQueue,其实现原理比较复杂,这里也不进行过多介绍,有机会可以在后期单独对其实现原理进行分析。