阻塞队列之PriorityBlockingQueue
上一篇文章笔主介绍了Java中的优先级队列,即PriorityQueue,了解了其内部的具体实现。今天,我们再来学习下Java中的优先级阻塞队列,即PriorityBlockingQueue。
PriorityBlockingQueue的定义
我们看下JDK中对的PriorityBlockingQueue定义,如下:
An unbounded {@linkplain BlockingQueue blocking queue} that uses the same ordering rules as class {@link PriorityQueue} and supplies blocking retrieval operations. While this queue is logically unbounded, attempted additions may fail due to resource exhaustion (causing {@code OutOfMemoryError}). This class does not permit {@code null} elements. A priority queue relying on {@linkplain Comparable natural ordering} also does not permit insertion of non-comparable objects (doing so results in {@code ClassCastException}).
PriorityBlockingQueue是与类PriorityQueue使用相同排序规则并且提供阻塞检索操作的无界阻塞队列。虽然此队列在逻辑上是无界的,但尝试添加元素时可能会失败,由于资源耗尽(导致OutOfMemoryError)。此队列不允许空的元素。依赖自然顺序排序的优先级队列也不允许插入不可比较的对象(这样做会导致ClassCastException)
Operations on this class make no guarantees about the ordering of elements with equal priority. If you need to enforce an
ordering, you can define custom classes or comparators that use a secondary key to break ties in primary priority values.
此类上的操作不能保证具有相同优先级的元素的顺序。如果需要强制有序,可以定义自定义类或比较器,使用辅助关键字来打破主要优先级的关系。
PriorityBlockingQueue类的特点
简单总结下,PriorityBlockingQueue有以下特点:
- PriorityBlockingQueue是一个无界队列,没有容量限制。
- PriorityBlockingQueue不允许插入空元素,如果提供了比较器,插入的元素就按照比较器排序。否则,按照自然顺序来排序。
- PriorityBlockingQueue不能保证具有相同优先级的元素的顺序。
接下来,我们从源码的角度来认识下PriorityBlockingQueue。
PriorityBlockingQueue类的成员属性
PriorityBlockingQueue主要定义了以下成员属性,源码如下:
//队列的默认初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//数组的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//队列基于数组实现,用来放置元素的数组,元素的优先级通过堆排序实现
private transient Object[] queue;
//队列中的元素个数
private transient int size;
//元素排序使用的比较器,如果comparator为null,则使用自然排序
private transient Comparator<? super E> comparator;
//所有对队列的操作都需要使用此锁
private final ReentrantLock lock;
//非空条件对象,队列为空时移除元素的线程会被阻塞
private final Condition notEmpty;
//扩容时需要使用此乐观锁,CAS实现
private transient volatile int allocationSpinLock;
//序列化和反序列化时会用到此属性,为了兼容老版本,只有在序列化和反序列化时不为null
private PriorityQueue<E> q;
// 获取Unsafe类的实例,使用Unsafe类实例获取乐观锁
private static final sun.misc.Unsafe UNSAFE;
//乐观锁的偏移量
private static final long allocationSpinLockOffset;
PriorityBlockingQueue类的UNSAFE和allocationSpinLockOffset是在静态代码块进行初始化的,源码如下:
static {
try {
//初始化Unsafe类实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = PriorityBlockingQueue.class;
allocationSpinLockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("allocationSpinLock"));
} catch (Exception e) {
throw new Error(e);
}
}
PriorityBlockingQueue类的构造方法
PriorityBlockingQueue提供了四种方式来构造阻塞队列,分别是:
- 默认无参构造方法,既不指定队列的初始容量,也不指定比较器。
- 指定初始容量的构造方法
- 指定初始容量和比较器的构造方法
- 使用给定集合构造队列的方法
构造方法的源码如下:
//默认无参构造方法
public PriorityBlockingQueue() {
//使用默认初始容量,指定比较器为nul,插入的元素使用自然排序
this(DEFAULT_INITIAL_CAPACITY, null);
}
//使用指定初始容量的构造方法
public PriorityBlockingQueue(int initialCapacity) {
//指定比较器为nul,插入的元素使用自然排序
this(initialCapacity, null);
}
//使用指定初始容量和比较器的构造方法
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
//使用给定集合构造队列的方法
public PriorityBlockingQueue(Collection<? extends E> c) {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
//此字段标识堆是否已经有序
boolean heapify = true; // true if not known to be in heap order
//此字段标识是否需要筛查
boolean screen = true; // true if must screen for nulls
if (c instanceof SortedSet<?>) { //如果是SortedSet类型,说明元素已经有序
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
heapify = false;
}
else if (c instanceof PriorityBlockingQueue<?>) {
PriorityBlockingQueue<? extends E> pq =
(PriorityBlockingQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
screen = false;
//如果是PriorityBlockingQueue类型,也说明元素已经有序
if (pq.getClass() == PriorityBlockingQueue.class) // exact match 精确匹配
heapify = false;
}
Object[] a = c.toArray();
int n = a.length;
// 如果集合c转化为数组不是Object数组,就将其转化为Object数组
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, n, Object[].class);
if (screen && (n == 1 || this.comparator != null)) {
for (int i = 0; i < n; ++i)
if (a[i] == null) //检查数组中是否有null元素
throw new NullPointerException();
}
this.queue = a; //数组直接赋值
this.size = n; //队列大小直接赋值
if (heapify)
//向小调整构建最小堆
heapify();
}
PriorityBlockingQueue类的成员方法
PriorityBlockingQueue类的成员方法可以划分为以下几类:
- 公共方法
- 插入方法
- 移除方法
这里公共方法是指在类内部会被多次调用的方法或者是获取队列属性的方法,例如:扩大数组容量的方法、查询队列头部元素的方法、获取队列比较器的方法、获取队列元素个数的方法、清空整个队列的方法、将队列转换为数组的方法等等。源码如下:
//数组扩容方法
private void tryGrow(Object[] array, int oldCap) {
/*
* tryGrow()是在offer()中被调用的,队列插入元素前需要判断是否需要扩容。
* 扩容时释放锁可以提高队列的吞吐量,防止扩容过程中生产者线程还占用锁而导致消费线程无法消费。
*/
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
//此处使用乐观锁,只有一个线程可以获得乐观锁然后进行扩容。
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
/**
* 如果当前容量小于64,新的容量等于当前容量的2倍再加2。
* 否则,新的容量等于当前容量的1.5倍。
**/
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));
// 如果新的容量大于规定的数组的最大容量,可能溢出
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
//新的容量等于当前容量+1
int minCap = oldCap + 1;
//minCap<0说明已经超出Integer能表示的最大数字,已经溢出
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
//抛出OutOfMemoryError
throw new OutOfMemoryError();
//限制最大容量
newCap = MAX_ARRAY_SIZE;
}
if (newCap > oldCap && queue == array)
//创建新数组
newArray = new Object[newCap];
} finally {
//使用完毕将其重新置为0
allocationSpinLock = 0;
}
}
//newArray==null说明是未获得乐观锁的线程
if (newArray == null) // back off if another thread is allocating
//主动让出CPU运行时间
Thread.yield();
//重新获取锁
lock.lock();
if (newArray != null && queue == array) {
queue = newArray;
//将旧的数组中的数组拷贝至新数组
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
//真正将元素移除队列的方法,只有获取到锁的线程通过offer()调用此方法
private E dequeue() {
int n = size - 1;
if (n < 0) //如果数组为空,直接返回null
return null;
else {
Object[] array = queue;
E result = (E) array[0]; //获取数组第一个元素
E x = (E) array[n]; //获取数组最后一个元素
array[n] = null; //将数组最后一个元素置为null
Comparator<? super E> cmp = comparator;
if (cmp == null)
//比较器为null时使用自然排序向下调整构造最小堆
siftDownComparable(0, x, array, n);
else
//比较器不为null时使用比较器排序向下调整构造最小堆
siftDownUsingComparator(0, x, array, n, cmp);
size = n; //改变size
return result;
}
}
//使用自然排序向上调整构造最小堆
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
//计算第k个节点的父节点下标
int parent = (k - 1) >>> 1;
Object e = array[parent];
//如果x大于第k个节点的父节点,说明已是最小堆,直接跳出循环
if (key.compareTo((T) e) >= 0)
break;
//交换第k个节点与父节点的元素
array[k] = e;
k = parent;
}
array[k] = key;
}
//使用比较器排序向上调整构造最小堆
private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
Comparator<? super T> cmp) {
while (k > 0) {
//计算第k个节点的父节点下标
int parent = (k - 1) >>> 1;
Object e = array[parent];
//如果x大于第k个节点的父节点,说明已是最小堆,直接跳出循环
if (cmp.compare(x, (T) e) >= 0)
break;
//交换第k个节点与父节点的元素
array[k] = e;
k = parent;
}
array[k] = x;
}
//使用自然排序向下调整构造最小堆
private static <T> 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) {
//计算第k个节点的左孩子节点下标,第k个节点的左孩子节点为2k+1
int child = (k << 1) + 1; // assume left child is least
Object c = array[child];
//计算第k个节点的右孩子节点下标,第k个节点的右孩子节点为2k+2
int right = child + 1;
//获取左右孩子中最小的节点元素
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
//如果key小于c,说明已经是最小堆,直接跳出循环
if (key.compareTo((T) c) <= 0)
break;
//将左右孩子中最小的节点放在第k个节点位置
array[k] = c;
k = child; //此处k重新赋值
}
//将元素x放在第k个节点位置
array[k] = key;
}
}
//使用比较器排序向下调整构造最小堆
private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
int n,
Comparator<? super T> cmp) {
if (n > 0) {
int half = n >>> 1;
while (k < half) {
//计算第k个节点的左孩子节点下标,第k个节点的左孩子节点为2k+1
int child = (k << 1) + 1;
Object c = array[child];
//计算第k个节点的右孩子节点下标,第k个节点的右孩子节点为2k+2
int right = child + 1;
//获取左右孩子中最小的节点元素
if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
c = array[child = right];
//如果key小于c,说明已经是最小堆,直接跳出循环
if (cmp.compare(x, (T) c) <= 0)
break;
//将左右孩子中最小的节点放在第k个节点位置
array[k] = c; //此处k重新赋值
k = child;
}
//将元素x放在第k个节点位置
array[k] = x;
}
}
//向下调整构建最小堆
private void heapify() {
Object[] array = queue;
int n = size;
int half = (n >>> 1) - 1;
Comparator<? super E> cmp = comparator;
if (cmp == null) {
//比较器为null使用自然排序构造最小堆
for (int i = half; i >= 0; i--)
siftDownComparable(i, (E) array[i], array, n);
}
else {
//比较器不为null,使用比较器构造最小堆
for (int i = half; i >= 0; i--)
siftDownUsingComparator(i, (E) array[i], array, n, cmp);
}
}
//查询队列头部元素
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();//获取锁
try {
//队列为空返回null,否则返回数组第一个元素
return (size == 0) ? null : (E) queue[0];
} finally {
lock.unlock(); //释放锁
}
}
//获取队列的比较器
public Comparator<? super E> comparator() {
return comparator;
}
//获取队列元素个数
public int size() {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
return size;
} finally {
lock.unlock(); //释放锁
}
}
//返回队列还可以容纳的容量,因为是无界队列直接返回Integer.MAX_VALUE
public int remainingCapacity() {
return Integer.MAX_VALUE;
}
//获取给定元素在数组中的位置
private int indexOf(Object o) {
if (o != null) {
Object[] array = queue;
int n = size;
for (int i = 0; i < n; i++) //遍历寻找
if (o.equals(array[i]))
return i;
}
return -1;
}
//删除给定下标上的元素
private void removeAt(int i) {
Object[] array = queue;
int n = size - 1;
//如果删除的是最后一个元素,直接将数组最后一个元素置为null
if (n == i) // removed last element
array[i] = null;
else {
//获取数组的最后一个元素
E moved = (E) array[n];
//将数组最后一个元素置为null
array[n] = null;
Comparator<? super E> cmp = comparator;
if (cmp == null)
//将最后一个元素插入到数组的i位置,使用自然排序向下调整构造最小堆
siftDownComparable(i, moved, array, n);
else
//将最后一个元素插入到数组的i位置,使用比较器向下调整构造最小堆
siftDownUsingComparator(i, moved, array, n, cmp);
//queue[i] == moved说明数组最后一个元素直接移动到了被删除元素的位置
if (array[i] == moved) {
//此时需要向上调整构造最小堆
if (cmp == null)
siftUpComparable(i, moved, array);
else
siftUpUsingComparator(i, moved, array, cmp);
}
}
size = n;
}
判断队列中是否包含给定的元素o
public boolean contains(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return indexOf(o) != -1;
} finally {
lock.unlock();
}
}
//将队列转换为Object数组
public Object[] toArray() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return Arrays.copyOf(queue, size);
} finally {
lock.unlock();
}
}
//使用字符串表示队列对象
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
int n = size;
if (n == 0)
return "[]";
//使用StringBuilder拼接队列元素
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < n; ++i) {
Object e = queue[i];
sb.append(e == this ? "(this Collection)" : e);
if (i != n - 1)
sb.append(',').append(' ');
}
return sb.append(']').toString();
} finally {
lock.unlock(); //释放锁
}
}
//将队列中的元素移除并放入集合中
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
//将队列中的元素移除并放入集合中
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
int n = Math.min(size, maxElements);
for (int i = 0; i < n; i++) {
c.add((E) queue[0]); // In this order, in case add() throws.
dequeue(); //执行移除队列操作
}
return n;
} finally {
lock.unlock(); //释放锁
}
}
//清空整个队列
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
Object[] array = queue;
int n = size;
size = 0;
//将数组元素全部置为null
for (int i = 0; i < n; i++)
array[i] = null;
} finally {
lock.unlock(); //释放锁
}
}
//将队列转换为T类型数组
public <T> T[] toArray(T[] a) {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
int n = size;
if (a.length < n)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(queue, size, a.getClass());
System.arraycopy(queue, 0, a, 0, n);
if (a.length > n)
a[n] = null;
return a;
} finally {
lock.unlock(); //释放锁
}
}
//返回队列的迭代器
public Iterator<E> iterator() {
return new Itr(toArray());
}
//将队列序列化至输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
lock.lock();
try {
// avoid zero capacity argument
q = new PriorityQueue<E>(Math.max(size, 1), comparator);
q.addAll(this);
s.defaultWriteObject();
} finally {
q = null;
lock.unlock();
}
}
//从输入流中反序列化出队列
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
try {
s.defaultReadObject();
int sz = q.size();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, sz);
this.queue = new Object[sz];
comparator = q.comparator();
addAll(q);
} finally {
q = null;
}
}
PriorityBlockingQueue插入操作提供了add(E e)、offer(E e)、put(E e)、offer(E e, long timeout, TimeUnit unit)这四个方法,实际上add(E e)、put(E e)、offer(E e, long timeout, TimeUnit unit)这个三个方法内部直接调用了offer(E e),这是正是因为PriorityBlockingQueue是无界队列,插入方法的源码如下:
//向队列中插入元素
public boolean add(E e) {
return offer(e);
}
//向队列中插入元素
public boolean offer(E e) {
//不能插入null元素
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
int n, cap;
Object[] array;
/**
* 当队列的大小等于数组长度时,说明数组需要扩容。
* 此处使用while循环是因为tryGrow()方法是先释放锁然后获取乐观锁扩容,
* 释放锁后有可能会有其他生产者线程也调用了tryGrow()。
* 多个生产者线程调用tryGrow()只有一个线程可以扩容成功,扩容失败的线程会循环调用tryGrow()直至扩容完成。
*/
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; //size+1
notEmpty.signal(); //通知阻塞的消费者线程
} finally {
lock.unlock(); //释放锁
}
return true;
}
//向队列中插入元素,因为是无界队列,插入操作不需要阻塞
public void put(E e) {
offer(e); // never need to block
}
//向队列中插入元素
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e); // never need to block
}
PriorityBlockingQueue移除操作提供了 poll()、take()、poll(long timeout, TimeUnit unit)、remove(Object o)、 removeEq(Object o)这五个方法,这五个方法的特点如下:
- poll():如果队列为空,会直接返回null。否则,移除队列头部的元素。
- take():如果队列为空,移除操作会阻塞直至有元素可以移除。
- poll(long timeout, TimeUnit unit):如果队列为空,移除操作在给定的时间内会阻塞。如果阻塞结束队列为空,会直接返回null。否则,移除队列头部的元素。
- remove(Object o):如果队列中存在给定的元素o,就通过for循环确定元素o的下标然后删除。此方法是public类型,可以在类外部直接被使用。
- removeEq(Object o):此方法是default类型,只可以在本类或同包的类中使用。另外,此方法主要供PriorityBlockingQueue的迭代器调用。
这五个方法源码如下:
//移除队列头部的元素
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
return dequeue(); //如果队列为空直接返回null
} finally {
lock.unlock(); //释放锁
}
}
//移除队列头部的元素,如果队列为空移除操作会阻塞直至有元素可以移除
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //获取锁
E result;
try {
while ( (result = dequeue()) == null)
notEmpty.await(); //队列为空阻塞移除操作
} finally {
lock.unlock(); //释放锁
}
return result;
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //获取锁
E result;
try {
while ( (result = dequeue()) == null && nanos > 0)
nanos = notEmpty.awaitNanos(nanos); //给定的时间内移除操作会阻塞
} finally {
lock.unlock(); //释放锁
}
return result;
}
//如果队列存在给定的元素o就删除
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
int i = indexOf(o); //获取元素o在数组中的下标
if (i == -1)
return false;
removeAt(i); //从队列中删除元素o
return true;
} finally {
lock.unlock(); //释放锁
}
}
//如果队列存在给定的元素o就删除
void removeEQ(Object o) {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
Object[] array = queue;
for (int i = 0, n = size; i < n; i++) {
if (o == array[i]) {
removeAt(i); //从队列中删除元素o
break;
}
}
} finally {
lock.unlock(); //释放锁
}
}
总结
至此,我们通过分析源码分方式学习了PriorityBlockingQueue。再次总结下其特点:
- PriorityBlockingQueue是一个无界阻塞队列,没有容量限制。
- PriorityBlockingQueue不允许插入空元素,如果提供了比较器,插入的元素就按照比较器排序。否则,按照自然顺序来排序。
- PriorityBlockingQueue的操作使用了ReentrantLock ,是线程安全的队列。
- PriorityBlockingQueue扩容时使用了乐观锁,只有获取到乐观锁的线程才可以对数组扩容。
- PriorityBlockingQueue的优先级是借助于内部数组的堆排序实现的,队列如果有元素插入或者删除,会进行堆的调整以重新构建最小堆。
由于笔主水平有限,笔误或者不当之处还请批评指正。