阻塞队列之PriorityBlockingQueue
PriorityBlockingQueue基本概念
- PriorityBlockingQueue不满足队列先进先出的原则
- PriorityBlockingQueue基于二叉堆实现,底层使用数组结构实现二叉堆
- PriorityBlockingQueue会将存入的数据进行排序
二叉堆介绍
二叉堆其实就是一个完整的二叉树。所谓完整的二叉树是指只有当一层节点已经添加满了,才会将元素添加到下一层
二叉堆分为小顶堆和大顶堆,PriorityBlockingQueue使用的是小顶堆
小顶堆是指任意子节点都比其父节点大。而大顶堆正好相反,任意子节点都比其父节点小
二叉堆是通过数组结构实现小顶堆的
PriorityBlockingQueue构造方法
PriorityBlockingQueue是无界队列。虽然其有个指定容量的构造方法,但是在添加元素的方法中,当容量不够时,还是会进行扩容,直到最大值MAX_ARRAY_SIZE。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
PriorityBlockingQueue核心属性
// 默认队列容量大小为11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 数组的最大容量。为了适配不同的JVM,所以设置成Integer.MAX_VALUE - 8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 优先级队列是底层是数组实现
private transient Object[] queue;
// 队列中元素个数
private transient int size;
// 用于比较元素大小的比较器
private transient Comparator<? super E> comparator;
private final ReentrantLock lock;
// 消费者的Condition
private final Condition notEmpty;
// 数组扩容时的标志属性。
// 数组在扩容时会释放锁,为了避免数组扩容与元素读取操作发生并发,设置了此标志属性
private transient volatile int allocationSpinLock;
// 优先级阻塞队列使用了很多优先级队列的功能
private PriorityQueue<E> q;
PriorityBlockingQueue存放元素方法
add方法
public boolean add(E e) {
return offer(e);
}
offer非阻塞方法
- 判断数据是否为空,如果为空,抛出异常
- 加锁
- 判断数组中元素个数是否达到数组最大值,如果达到,数组扩容
- 如果数组容量小于等于64,扩容成原来容量的2倍+2
- 如果数组容量大于64,扩容成原来的1.5倍
- 判断扩容后数组的容量是否达到最大值,如果达到最大值
- 判断扩容前数组容量是否已达到最大值,如果达到,抛出异常
- 如果未达到,设置扩容后的数组容量为最大值
- 判断比较器是否为空,如果为空,使用默认比较器,如果不为空,使用指定比较器
- 数据放入数组中,并且排序成小顶堆结构
- 数组中元素个数size值+1
- 唤醒消费者
- 释放锁
public boolean offer(E e) {
if (e == null)
// 参数为空,抛异常
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
// n是元素个数,cap是数组容量
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
// 元素个数>=数组容量,扩容
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
// 将元素放入数组中,并且与数组中的元素比较大小,保持小顶堆结构
if (cmp == null)
// 初始化PriorityBlockingQueue时没有指定比较器,走这里
siftUpComparable(n, e, array);
else
// 初始化PriorityBlockingQueue时指定了比较器,走这里
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
// 唤醒消费者
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
// 队列扩容的方法
private void tryGrow(Object[] array, int oldCap) {
// 扩容时是释放锁的
lock.unlock();
Object[] newArray = null;
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) {
// 进入此处说明当前线程拿到给数组扩容的资格,并且已经将扩容标志属性设置为1
try {
// 如果数组旧容量<64,扩容成旧容量的2倍再+2
// 这是为了在小容量的时候可以加速扩容
// 如果数组旧容量>=64,扩容成旧容量的1.5倍,
// 因为此时的旧容量已经比较大了,1.5倍的扩容速度比较好
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) :
(oldCap >> 1));
if (newCap - MAX_ARRAY_SIZE > 0) {
// 进入此处说明新容量比数组最大容量还大
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 {
// 将扩容标志属性设置为0
allocationSpinLock = 0;
}
}
if (newArray == null)
// 当前线程进入此方法,但是没有给数组扩容的资格,想等待其他线程扩容
Thread.yield();
// 重新竞争锁
lock.lock();
if (newArray != null && queue == array) {
// 进入此处说明没有发生扩容并发,将新数组赋值给queue
queue = newArray;
// 旧数组中的元素迁移到新数组中
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
// k:队列元素个数;x:要存放的元素;array:存元素的数组
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赋值为父节点索引
// 准备在下次循环中,让当前元素与父节点的父节点再比较
k = parent;
}
// 到这里,有两种情况
// 第一种,数组中没有元素,当前元素放在第一位就可以了
// 第二种,数组经过while循环,已经找到自己的位置
array[k] = key;
}
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;
}
offer阻塞方法
// 因为PriorityBlockingQueue是无界队列,所以存放数据不会阻塞
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e); // never need to block
}
put方法
public void put(E e) {
offer(e);
}
PriorityBlockingQueue获取元素方法
remove方法
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
poll无参方法
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// dequeue方法拿到数据,并且保持小顶堆结构
return dequeue();
} finally {
lock.unlock();
}
}
poll有参方法
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;
}
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;
}
private E dequeue() {
// n为最后一个元素的索引
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;
}
}
// k:默认值0;x:数组中最后一个元素;array:数组;n:最后一个元素索引
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;
// 因为是小顶堆结构
// 所以只用整理一半的树即可
while (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
c = array[child = right];
// 判断数组最后一个元素是否小于等于左右子节点中较小的那个节点
if (key.compareTo((T) c) <= 0)
// 最后一个元素小于等于左右子节点中较小的那个节点
// 结束循环
break;
array[k] = c;
k = child;
}
array[k] = key;
}
}