基于jdk1.8源码进行分析的。
看完了两个BlockingQueue的实现类,下面我们来看一下PriorityBlockingQueue的实现,也就是有优先级的阻塞队列。根据前面的经验大致猜测,感觉下来,知道了对应的源码实现过程,下面去验证一下自己的想法是否正确。
类继承结构
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
继承了AbstractQueue类,实现了BlockingQueue接口和Serializable接口。
成员属性
//默认的初始化大小
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//这个数组代表的是一个平衡二叉堆,即queue[n]的子节点为queue[2n+1]和queue[2*(n+1)]
private transient Object[] queue;
//优先队列中的元素个数
private transient int size;
//比较器,如果为空,则为自然顺序
private transient Comparator<? super E> comparator;
//lock锁
private final ReentrantLock lock;
//为空时,进行阻塞的Condition
private final Condition notEmpty;
//优先队列:主要用于序列化,这是为了兼容之前的版本。只有在序列化和反序列化才非空
private PriorityQueue<E> q;
构造方法
PriorityBlockingQueue()
默认构造方法,默认容量大小为11,没有比较器,按照自然顺序排序。
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
通过源码看到,是通过调用另外一个构造方法实现的。
PriorityBlockingQueue(int initialCapacity)
初始化的时候指定容量大小,按照自然顺序进行排序。
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
通过源码看到,是通过调用另外一个构造方法实现的。
public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
//数据合法性校验
if (initialCapacity < 1)
//不合法,抛出异常
throw new IllegalArgumentException();
//初始化的时候初始化锁
this.lock = new ReentrantLock();
//初始化condition
this.notEmpty = lock.newCondition();
//构造器初始化
this.comparator = comparator;
//初始化数组,二叉堆
this.queue = new Object[initialCapacity];
}
初始化的时候,可以指定容量大小以及对应的元素比较器。
核心方法
put(E e)
public void put(E e) {
offer(e); // never need to block
}
可以看到是通过调用内部方法offer实现的。
offer(E e)
public boolean offer(E e) {
//数据合法性校验
if (e == null)
//不合法抛出异常
throw new NullPointerException();
//记录lock锁
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
//声明一些变量
int n, cap;
//声明一个数组
Object[] array;
//n=size
//array=queue
//cap = queue.length
//如果此时数组已满,则需要扩容。
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大小
size = n + 1;
//唤醒消费者线程
notEmpty.signal();
} finally {
//释放锁
lock.unlock();
}
//返回结果
return true;
}
tryGrow(Object[] array, int oldCap)
二叉堆扩容方法
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); //释放获取的锁
Object[] newArray = null;
//cas成功则扩容(4)
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
//oldGap<64则扩容新增oldcap+2,否者扩容50%,并且最大为MAX_ARRAY_SIZE
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 {
allocationSpinLock = 0;
}
}
//第一个线程cas成功后,第二个线程会进入这个地方,然后第二个线程让出cpu,尽量让第一个线程执行下面点获取锁,但是这得不到肯定的保证。(5)
if (newArray == null) // 如果两外一个线程正在分配,则让出
Thread.yield();
lock.lock();//(6)
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
可以看到这里先释放了锁,当然不释放锁也是可以的。这话怎么讲?也就意味着整个扩容阶段和过程都持有对应的锁,那么对于其他线程来讲就只能阻塞了,等待扩容,操作完毕释放锁,那么操作如果比较耗时,也就意味着并发性能比较差。所以为了提高性能,使用CAS控制只有一个线程可以进行扩容,并且在扩容前释放了锁,让其他线程可以进行入队和出队操作。spinlock锁使用CAS控制只有一个线程可以进行扩容,CAS失败的线程会调用Thread.yield() 让出 cpu,目的是为了让扩容线程扩容后优先调用 lock.lock 重新获取锁,但是这得不到一定的保证。有可能yield的线程在扩容线程扩容完成前已经退出,并获取到了锁。如果当前数组扩容还没完毕,当前线程会再次调用tryGrow方法,然后释放锁,这又给扩容线程获取锁提供了机会,如果这时候扩容线程还没扩容完毕,则当前线程释放锁后又调用yield方法让出CPU。可知当扩容线程进行扩容期间,其他线程是原地自旋通过代码检查当前扩容是否完毕,等扩容完毕后才退出代码的循环。当扩容线程扩容完毕后会重置自旋锁变量allocationSpinLock 为 0,这里并没有使用UNSAFE方法的CAS进行设置是因为同时只可能有一个线程获取了该锁,并且 allocationSpinLock 被修饰为了 volatile。当扩容线程扩容完毕后会获取锁,获取锁后复制当前 queue 里面的元素到新数组。
siftUpComparable(int k, T x, Object[] array)
没有指定比较器,也就是采用自然顺序进行比较的时候,此时调用的方法,也就是整个二叉堆建堆的过程。
//k = size , x = e, array = queue
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
//队列元素个数>0则判断插入位置,否者直接入队
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;
}
实现思路如下:
首先把要添加的元素加到数组的末尾,然后和它的父节点(位置为当前位置减去1再除以2取整(k-1)/2,比如第4个元素的父节点位置是1,第7个元素的父节点位置是3)比较,如果新元素比父节点元素大则交换这两个元素,然后再和新位置的父节点比较,直到它的父节点不再比它小,或者已经到达顶端,即第1的位置。
siftUpUsingComparator(int k, T x, Object[] array, Comparator<? super T> cmp)
使用比较器来构建堆。这里可以看到跟前面相比,最大的区别就在于比较器。
//k = size , x = e, array = queue cmp=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;
}
下面我们来看一下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;
}
dequeue()
出队操作具体实现
private E dequeue() {
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;
}
}
整个都是关于二叉堆的操作。思路大体如下:删除位置1的元素,把最后一个元素移到最前面,然后和它的两个子节点比较,如果两个子节点中较小的节点小于该节点,就将它们交换,直到两个子节点都比此顶点大。
涉及到的方法源码如下:
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
//在数组中,位置k的节点的子节点的下标肯定小于half
while (k < half) {
//左子节点的下标
int child = (k << 1) + 1; // assume left child is least
Object c = array[child];
//右子节点的下标
int right = child + 1;
//先比较左右子节点谁小,始终保持c为两子节点中最小的。
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
//如果key值大于子节点的值,则向后沉。否则不变
if (key.compareTo((T) c) <= 0)
break;
array[k] = c;
k = child;
}
array[k] = key;
}
}
//函数功能:将元素x插入到array[k]处,并自己指定的比较器进行相应的调整,使之保持为最小堆
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;
}
}
以上就是整个核心函数的源码分析过程了,如果不熟悉二叉堆的相关操作,可能看起来稍微有些吃力。不过我们可以看一下DEMO演示。
package demo;
import java.util.concurrent.PriorityBlockingQueue;
/**
* PriorityBlockingQueue测试例程
* @ClassName: PriorityBlockingQueueDemo
* @Description: PriorityBlockingQueue测试例程
* @author BurgessLee
* @date 2019年5月5日
*
*/
public class PriorityBlockingQueueDemo {
public static void main(String[] args) {
PriorityBlockingQueue<ComparableEntity> priorityBlockingQueue = new PriorityBlockingQueue<ComparableEntity>();
ComparableEntity c1 = new ComparableEntity(1);
ComparableEntity c2 = new ComparableEntity(1);
ComparableEntity c3 = new ComparableEntity(1);
priorityBlockingQueue.put(c1);
priorityBlockingQueue.put(c2);
priorityBlockingQueue.put(c3);
System.out.println("调用take之前" +priorityBlockingQueue);
try {
ComparableEntity res = priorityBlockingQueue.take();
System.out.println("调用take方法,返回的结果是res=" + res.getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("调用take之后" +priorityBlockingQueue);
}
}
class ComparableEntity implements Comparable<ComparableEntity>{
private Integer id;
public ComparableEntity() {
super();
}
public ComparableEntity(Integer id) {
super();
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public int compareTo(ComparableEntity o) {
return this.id.compareTo(o.id);
}
}
输出结果:
调用take之前[demo.ComparableEntity@4e25154f, demo.ComparableEntity@70dea4e, demo.ComparableEntity@5c647e05]
调用take方法,返回的结果是res=1
调用take之后[demo.ComparableEntity@5c647e05, demo.ComparableEntity@70dea4e]
以上就是所有演示代码测试例程了。如果有不对的地方,还请指正。