1、PriorityBlockingQueue 简介
(1)PriorityBlockingQueue整个入队出队的过程与PriorityQueue基本是保持一致的;
(2)PriorityBlockingQueue使用一个锁+一个notEmpty条件控制并发安全;
(3)PriorityBlockingQueue扩容时使用一个单独变量的CAS操作来控制只有一个线程进行扩容;
(4)入队使用自下而上的堆化;
(5)出队使用自上而下的堆化;
该阻塞队列每次取出的都是最小的对象(小顶堆!!!),可以满足一定的实际场景
阻塞队列PriorityBlockingQueue从不阻塞写线程,而当队列元素为空时,会阻塞读线程的读取,当然也有非阻塞的方法(poll)。该阻塞队列适用于读多于写的场景,不然,写线程过多,会导致内存消耗过大,影响性能。
(1)写线程不阻塞,读线程在队列为空时阻塞
当队列为满时,写线程不会阻塞,而会尝试去扩容,扩容成功就继续向阻塞队列写入数据。当队列为空时,读线程会阻塞等待,直到队列不为空,被写线程唤醒。因此该阻塞队列适用于读多于写的场景,不然,写线程过多,会导致内存消耗过大,影响性能。读写线程共用同一把独占锁。
(2)堆存储结构
阻塞队列PriorityBlockingQueue每次取出的都是最小(或最大)的对象,而其底层的存储结构正是堆存储(ps:堆结构需要满足每一个父节点小于(或大于)每一个子节点),所以每次向阻塞队列添加的时候,需要根据元素大小,调整其在堆中的位置,注意不需要对所有元素排序,只对父子节点排序,只要满足堆性质即可。这样每次从阻塞队列中取出元素时,直接取出堆顶元素,即最小值(或最大值),然后重新调整堆。
(3)扩容机制
当阻塞队列PriorityBlockingQueue满时,写线程会尝试扩容(扩增old+2或者old/2),注意,写线程扩容的时候会先释放独占锁,并获取一个扩容独占锁(此锁仅用于扩容,防止多个写线程同时扩容),这样做的目的是提高读线程的吞吐量。扩容的本质其实就是新建一个更大的object数组,然后把阻塞队列PriorityBlockingQueue中的原阻塞队列引用替换成新的数组。
(4)PriorityBlockingQueue需要元素自然排序或者提供比较器
前面说过,阻塞队列PriorityBlockingQueue使用的是堆存储结构,那么就需要对父子节点进行排序,所以需要阻塞队列PriorityBlockingQueue存储的元素实现了Comparable接口,或者在新建阻塞队列PriorityBlockingQueue的时候提供比较器。
(1)依然是使用一个数组来使用元素;
(2)使用一个锁加一个notEmpty条件来保证并发安全;
(3)使用一个变量的CAS操作来控制扩容;
2、成员变量 和 构造方法
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// 序列号
private static final long serialVersionUID = 5595510919245408276L;
// 默认数组容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 能分配的最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 构成queue的数组
private transient Object[] queue;
// queue 中当前的元素数量
private transient int size;
// 比较器,如果优先级队列使用元素自然排序,则为空
private transient Comparator<? super E> comparator;
// 独占锁,读写线程共用这一把锁
private final ReentrantLock lock;
// 读线程等待队列,写线程永远不会阻塞
private final Condition notEmpty;
// 写线程扩容锁,通过CAS控制,只有一个写线程会将此变量从0变成1
private transient volatile int allocationSpinLock;
// 不阻塞的优先级队列,非存储元素的地方,仅用于序列化/反序列化时
private PriorityQueue<E> q;
private static final sun.misc.Unsafe UNSAFE;
private static final long allocationSpinLockOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = PriorityBlockingQueue.class;
allocationSpinLockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("allocationSpinLock"));
} catch (Exception e) {
throw new Error(e);
}
}
/********************* 下面是构造方法 ***********************/
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.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
// 将参数集合c中的元素放入queue
public PriorityBlockingQueue(Collection<? extends E> c) {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
boolean heapify = true; // 不知道堆的order时,尾true
boolean screen = true; // 如果必须筛选空值,则为true
if (c instanceof 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;
if (pq.getClass() == PriorityBlockingQueue.class) // exact match
heapify = false;
}
Object[] a = c.toArray();
int n = a.length;
// If c.toArray incorrectly doesn't return Object[], copy it.
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)
throw new NullPointerException();
}
this.queue = a;
this.size = n;
if (heapify)
heapify();
}
}
3、方法介绍
扩容tryGrow() ,将元素入队时,如果容量满了,就扩容。越小扩容越快
增加元素的4个方法:add(E e)、offer(E e) 、offer(E e, long timeout, TimeUnit unit)、put(E e) 都不阻塞或等待。因为如果容量满,就扩容。
删除元素的方法里面,take() 还是个阻塞方法。
不管是 添加元素 还是 删除元素,都要加锁,而且 是同一把锁
siftUpComparable、siftUpUsingComparator、siftDownComparable、siftDownUsingComparator 方法体现了堆排序的思想
3.1、扩容 | 建堆
tryGrow(Object[] array, int oldCap)
// 写线程扩容
private void tryGrow(Object[] array, int oldCap) {
// 先释放锁,因为是从offer()方法的锁内部过来的
// 这里先释放锁,使用allocationSpinLock变量控制扩容的过程
// 防止阻塞的线程过多
lock.unlock();
Object[] newArray = null;
// CAS更新allocationSpinLock变量为1的线程获得扩容资格
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) {
try {
// 确定新的容量;小时增长的快
// 当oldCap<64时,newCap = oldCap + (oldCap+2)
// 当oldCap>=64时,newCap = oldCap + oldCap/2;
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : (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 {
// 相当于解锁,这里可以不用CAS,因为只有一个线程能获取allocationSpinLock成功
allocationSpinLock = 0;
}
}
// 如果有其他线程正在扩容,则当前线程进入睡眠
if (newArray == null)
Thread.yield();
// 再次加锁
lock.lock();
// 判断新数组创建成功并且旧数组没有被替换过
if (newArray != null && queue == array) {
queue = newArray; // 队列赋值为新数组
System.arraycopy(array, 0, newArray, 0, oldCap); //并拷贝旧数组元素到新数组中
}
}
heapify()
// 对数组queue进行键堆
// 就是从下向上(half-->0)作为起点,依次向下遍历调整位置
// 就再构造方法里面用过
private void heapify() {
Object[] array = queue;
int n = size;
int half = (n >>> 1) - 1;
Comparator<? super E> cmp = comparator;
if (cmp == null) {
for (int i = half; i >= 0; i--)
siftDownComparable(i, (E) array[i], array, n);
}
else {
for (int i = half; i >= 0; i--)
siftDownUsingComparator(i, (E) array[i], array, n, cmp);
}
}
3.2、增加数据
add(E e)
public boolean add(E e) {
return offer(e);
}
offer(E e)
// 将数据e放入队列,立刻返回
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;
// 插入元素是插到堆底,原本是有序的
// 根据是否有比较器选择不同的方法,将e放到堆中合适的地方(从n堆底开始向上遍历)
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1; // queue中元素个数+1
notEmpty.signal(); //释放非空信号,唤醒take|poll等线程
} finally {
lock.unlock(); // 释放锁
}
return true;
}
offer(E e, long timeout, TimeUnit unit) 从不阻塞
// 不会进行超时等待,因为入队不需要等待,容量不够则扩容!!!
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e); // 从不阻塞
}
put(E e) 从不阻塞
public void put(E e) {
offer(e); // 从不阻塞,理由同上
}
3.3、删除数据
dequeue()
// 出队
private E dequeue() {
int n = size - 1; //计算出队后,queue的元素个数
// 如果当前queue为空,则返回null
if (n < 0)
return null;
else {
Object[] array = queue;
E result = (E) array[0]; // 取出队首元素
// 将队尾元素取出来赋给x,并将原来的队尾置空(!!!)
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;
}
}
poll() 立刻返回
// 出队,实际上调用的 dequeue()
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
return dequeue(); //调用出队函数
} finally {
lock.unlock(); //释放锁
}
}
poll(long timeout, TimeUnit unit) 超时等待
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout); //转换时间格式
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //加锁,响应中断
E result;
try {
// 如果queue为空,取出来的元素为空,则等待
while ( (result = dequeue()) == null && nanos > 0)
nanos = notEmpty.awaitNanos(nanos);
} finally {
lock.unlock(); // 释放锁
}
return result;
}
take() 阻塞方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 加锁,可中断
E result;
try {
// 如果queue为空,则进入等待队列
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
lock.unlock(); //释放锁
}
return result;
}
removeAt(int i)
注意,这是个private方法。 只被 remove(Object o) 和 removeEQ(Object o) 调用。
// 删除queue中的i处的元素
// 注意:没有加锁。 因为在调用本方法之前,已经加锁了
private void removeAt(int i) {
Object[] array = queue;
int n = size - 1;
// 情况1:要删除的元素是最后一个元素,则直接置空即可
if (n == i)
array[i] = null;
// 情况2:要删除的元素不是最后一个元素,处理较复杂
else {
// 将最后一个元素赋值给moved,并将array[n]置空
E moved = (E) array[n];
array[n] = null;
// 根据比较器的情况来调用不同的方法,将moved放到i-->n之间的合适位置
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftDownComparable(i, moved, array, n);
else
siftDownUsingComparator(i, moved, array, n, cmp);
// 如果moved没有放到i-->n之间,那么moved可能会放到0-->i之间
if (array[i] == moved) {
if (cmp == null)
siftUpComparable(i, moved, array);
else
siftUpUsingComparator(i, moved, array, cmp);
}
}
size = n; //更新size
}
remove(Object o)
删除的是 和o.equals()相同的
// 删除元素o
注意:和removeAt(int)不同的是,本方法加锁了,因为是public类型,可自由调用
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); //调用removeAt(int)方法删除,注意,此时还是持有锁的
return true;
} finally {
lock.unlock(); //释放锁
}
}
removeEQ(Object o)
删除的是 0==array[i]相同的,如果queue存的是对象,那么删除的是同一个对象(内存地址相同)
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);
break;
}
}
} finally {
lock.unlock();
}
}
3.4、遍历排序
siftUpComparable(int k, T x, Object[] array)
直接和 parent 比较
// 向上遍历排序,并插入新元素x
// k表示开始向上遍历位置的索引
// x表示插入元素的值,也就是本来是插到k处,但是经过排序之后旧可能不在k处的新元素的值
private static <T> 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; //插入新元素
}
siftUpUsingComparator(int k, T x, Object[] array, Comparator<? super T> cmp)
// 向上遍历排序,并插入新元素x
// k是开始线上遍历位置的索引
// x是新元素的值
// cmp 是自定义的比较器
private static <T> 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; //插入新元素
}
siftDownComparable(int k, T x, Object[] array, int n)
就是先找到 左右孩子中的较小者,再和 parent 比较
// 向下遍历排序,调整位置
// x是新堆顶的值(也就是原来堆中的最后一个值)
// k是开始向下遍历位置的索引,一般是0
// n是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; // n减半,注意:堆中,父比子小
// 只需要遍历到叶子节点就够了
while (k < half) { // 如果k==half说明是叶子节点;不可能出现k>half
// 左子节点
int child = (k << 1) + 1;
Object c = array[child];
// 右子节点
int right = child + 1;
// 取左右子节点中最小的值
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
// key如果比左右子节点都小,则堆化结束
if (key.compareTo((T) c) <= 0)
break;
// 否则,交换key与左右子节点中最小的节点的位置
array[k] = c;
k = child;
}
// 找到了放元素的位置,放置元素
array[k] = key;
}
}
siftDownUsingComparator(int k, T x, Object[] array, int n, Comparator<? super T> cmp)
// 向下遍历排序
// x是新堆顶的值(也就是原来堆中的最后一个值)
// k是开始向下遍历位置的索引,一般是0
// n是x再原来堆中的位置,一般是最后
// cmp是自带的比较器
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) {
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;
}
}
3.5、访问元素
peek()
// 访问队头元素
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
return (size == 0) ? null : (E) queue[0];
} finally {
lock.unlock();
}
}
3.6、查询
size()
// 返回queue中的元素个数
public int size() {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
return size;
} finally {
lock.unlock();
}
}
remainingCapacity()
// 返回queue剩余容量。一位内可以扩容,故为Integer.MAX_VALUE
public int remainingCapacity() {
return Integer.MAX_VALUE;
}
indexOf(Object o)
// 返回object再queue中的索引位置,不用加锁
private int indexOf(Object o) {
if (o != null) {
Object[] array = queue;
int n = size;
// 遍历queue数组查找
for (int i = 0; i < n; i++)
if (o.equals(array[i]))
return i;
}
return -1; //不存在则返回-1
}
contains(Object o)
// 判断queue是否包含o
public boolean contains(Object o) {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
return indexOf(o) != -1; //起始就是调用indexOf()方法
} finally {
lock.unlock();
}
}
3.7、将queue中数据输出到其他数据结构
toArray()
// 将queue中数据复制到数组中
public Object[] toArray() {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
return Arrays.copyOf(queue, size); //复制
} finally {
lock.unlock();
}
}
toArray(T[] a)
// 将queue中数据复制到特定类型的数组中
public <T> T[] toArray(T[] a) {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
int n = size;
// 通过反射创建特定类型的数组
if (a.length < n)
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();
}
}
drainTo(Collection<? super E> c)
// 将queue数据 迁移到集合c中,默认最大值
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
drainTo(Collection<? super E> c, int maxElements)
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);
// 遍历,迁移n个数据到集合c
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();
}
}
3.8、其他方法
iterator()
// 返回迭代器
public Iterator<E> iterator() {
return new Itr(toArray());
}
spliterator()
// 返回 可分割迭代器
public Spliterator<E> spliterator() {
return new PBQSpliterator<E>(this, null, 0, -1);
}
comparator()
// 返回比较器
public Comparator<? super E> comparator() {
return comparator;
}
toString()
// 打印输出
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
int n = size;
if (n == 0)
return "[]";
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();
}
}
clear()
// 清空
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock(); //加锁
try {
Object[] array = queue;
int n = size;
size = 0;
for (int i = 0; i < n; i++)
array[i] = null;
} finally {
lock.unlock();
}
}
writeObject(java.io.ObjectOutputStream s)
// 将queue数据输出到Stream(就是序列化)
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();
}
}
readObject(java.io.ObjectInputStream s)
// 用于反序列化
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;
}
}