什么是PriorityBlockingQueue?
PriorityBlockingQueue是JUC中一个线程安全的无界阻塞优先级队列,它结合了PriorityQyeye以及BlockingQueue的特性。
与LinkedBlockingQueue以及ArrayBlockingQueue不同,PriorityBlockingQueue中的元素按照优先级出队,而不是FIFO。
因此PriorityBlockingQueue中的元素必须实现Comparable<T>,或者在构造时传入Comparator<T>
PriorityBlockingQueue的内部数据结构
private static final int DEFAULT_INITIAL_CAPACITY = 11;
private transient Object[] queue;
private transient int size;
private transient Comparator<? super E> comparator;
private final ReentrantLock lock = new ReentrantLock();
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notEmpty = lock.newCondition();
private transient volatile int allocationSpinLock;
private PriorityQueue<E> q;
queue是真正的存储结构,代表一个平衡二叉堆。size代表它的大小。DEFAULT_INITIAL_CAPACITY是默认情况下,queue的大小。
comparator是排序规则,如果为null,代表使用元素自身的自然排序。
ReentrantLock是用于内部并发控制的唯一一把锁。notEmpty是空队列等待条件。
allocationSpinLock是扩容自旋锁,状态为1(扩容状态),或者0(没有进行扩容状态)。因此扩容不是在持有锁的情况下进行的。
q是为了兼容老版本而做出的字段。
PriorityBlockingQueue的方法
构造方法
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.comparator = comparator;
this.queue = new Object[Math.max(1, initialCapacity)];
}
在无参构造中,使用默认大小构造初始的队列大小,并且使用元素的自然排序。
其余构造方法可以指定队列的大小以及元素的排序规则。
offer操作
offer方法向队列中插入一个元素,且是不阻塞方法,又因为PriorityBlockingQueue是无界队列,因此必定返回true。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] es;
while ((n = size) >= (cap = (es = queue).length))
tryGrow(es, cap);
try {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftUpComparable(n, e, es);
else
siftUpUsingComparator(n, e, es, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
if (allocationSpinLock == 0 &&
ALLOCATIONSPINLOCK.compareAndSet(this, 0, 1)) {
try {
int growth = (oldCap < 64)
? (oldCap + 2) // grow faster if small
: (oldCap >> 1);
int newCap = ArraysSupport.newLength(oldCap, 1, growth);
if (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);
}
}
-
加锁
-
循环判断此时判断当前
size大于等于queue的容量。则调用tryGrow- 在扩容的时候,先释放主锁
- 判断自旋锁是否被持有并尝试获取自旋锁,保证只有一个线程在扩容
- 如果
newArray为null,说明有其他线程在扩容,将CPU释放一次 - 重新获取锁
- 如果新数组还没被赋值,则完成赋值动作。
-
如果
comparator为null,那么就使用元素自然排序,否则使用自定义的排序规则 -
容量加一,唤醒
take线程 -
解锁
PriorityBlockingQueue的扩容规则如下:
- 当容量小于64,使用慢速扩容,每次只加2
- 当容量大于等于64,每次扩大为2倍
在扩容的过程中释放锁,增大了并发性。
put操作
因为是无界队列,因此put操作不需要被阻塞,所以put操作和offer操作完全一致。
public void put(E e) {
offer(e); // never need to block
}
poll操作
poll操作的作用是获取队列内部堆树的根节点元素,如果队列为空,则返回null。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
- 加锁
- 出队
- 释放锁
take操作
take操作的作用是获取队列内部堆树的根节点元素,如果队列为空则阻塞。
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;
}
- 加锁
- 循环尝试出队,出队为空则阻塞
- 解锁
size方法
PriorityBlockingQueue使用size方法获取队列大小也是会加锁的,因此它也是准确值。
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return size;
} finally {
lock.unlock();
}
}