PriorityBlockingQueue简介
PriorityBlockingQueue是PriorityQueue的并发容器,是线程安全的。
PriorityQueue是一个完全二叉树,对每个节点都进行编号。
如果从1开始编号的话,parent和节点有一定的关系。
parent.id = child.id / 2;
leftChild.id = parent.id * 2 ;
rightChild.id = parent.id * 2 + 1;
由于这个特性,所以PriorityQueue可以用数组来存储,十分方便!
PriorityBlockingQueue源码分析
主要属性
//存放数据的数组
private transient Object[] queue;
//元素个数
private transient int size;
//排序规则
private transient Comparator<? super E> comparator;
//独占锁
private final ReentrantLock lock;
//队列为空的时候的阻塞队列
private final Condition notEmpty;
offer()
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)
//如果cmp==null,按照key的compare()方法来排列
siftUpComparable(n, e, array);
else//按照cmp排列
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;//size++
//唤醒出队等待线程
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
//siftUpUsingComparator()是往上堆化的过程。向上堆化过程也十分简单,就是如果child节点与parent节点不符合排序规则,就将child和parent换位置。
private static <T> void siftUpUsingComparator(int k, T x, Object[] array, Comparator<? super T> cmp) {
while (k > 0) {//k就是当前节点的下标,最小为0
//获取parent下标,parent是从0开始编号,感兴趣的同学可以自己去模拟一下parent和child之间的关系。
int parent = (k - 1) >>> 1;
Object e = array[parent];
//如果compare()>=0,说明就满足排序规则了
if (cmp.compare(x, (T) e) >= 0)
break;
//将parent的位置与child调换位置
array[k] = e;
k = parent;
}
array[k] = x;
}
add()
public boolean add(E e) {
return offer(e);
}
add()和offer()效果一样
put()
public void put(E e) {
offer(e); // never need to block
}
put()、add()、offer()效果一样
take()
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//可中断式申请锁
lock.lockInterruptibly();
E result;
try {
//如果队列为空,就阻塞在notEmpty队列上
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
lock.unlock();
}
return result;
}
private E dequeue() {
int n = size - 1;
//如果队列为空,返回null
if (n < 0)
return null;
else {
//第0个元素就是栈顶元素
Object[] array = queue;
E result = (E) array[0];
//将第n个元素设置为栈顶元素,然后利用array[n]作为标准来向下堆化
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;
}
}
siftDownUsingComparator()
向下堆化,就是从左儿子和右儿子之中跳出符合排序规则的点与parent调换,然后再将被调换的儿子节点作为parent节点继续执行这个过程,直至符合排序规则
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;
//如果右儿子比左儿子更满足排序规则,就将c设置为右儿子
if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
c = array[child = right];
//如果两个儿子和parent相比都不满足规则,就退出循环,向下堆化结束
if (cmp.compare(x, (T) c) <= 0)
break;
//如果儿子节点满足排序规则,就将儿子节点挪到父亲节点上
array[k] = c;
k = child;
}
array[k] = x;
}
}
poll()
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
与take()相比,队列为空时,poll()不会阻塞,会直接返回null。
总结
PriorityBlockingQueue就是引入了一个ReentrantLock来保证了线程并发安全,其他和PriorityQueue基本一致!