【JUC源码解析】PriorityBlockingQueue

简介

基于数据结构堆实现的线程安全的无界队列,这个堆的内存结构是数组,结合了数组和二叉树的特点。

以下内容参考《编程珠玑》和《算法导论》有关堆的章节。

数据结构

堆是用来表示元素集合的一种数据结构。

性质

顺序,任何结点的值都小于(大或于)等于其子结点的值。

形状,是一个数组,却可以被看成一个近似的完全二叉树,除了底层外,该树是完全充满的,而且是从左往右填充。

如上图所示,堆使用的是从下标1开始的数组,常见的方法如下:

root = 1
value(i) = x[i]
leftChild(i) = 2*i
rightChild(i) = 2*i + 1
parent(i) = i/2
null(i) = (i < 1) || (i > n)
 

heap(l, u) 定义如下:

x[i/2] <= x[i], (2*l <= x <= u)

 

关键方法

siftUp

heap(1,n-1) -> heap(1,n)

当x[1..n-1]是一个堆时,在x[n]中放置一个任意元素可能无法产生heap(1,n),可以使用siftUp函数来重新获得堆性质

尽可能地将新元素向上筛选(交换该结点与其父结点),树的根为x[1],位于树的顶部,x[n]位于数组的底部。

下图(从上到下)演示了新元素13在堆中向上筛选,直到到达合适的位置(这里是成为了根的右子结点)的过程。

步骤一,添加新元素13,橙色结点

 

 

步骤二,比17小,与其父结点17进行交换

 

步骤三,比15小,与其父节点15结点,到达合适位置(比12大) 

 

 

 代码实现siftUp方法,这里用Java语言实现

它的运行时间和logn成正比,因为堆具有logn层。

 1     void siftUp(int index, int key, int[] array) {
 2         while (index > 0) { // index是新增元素初始时下标,也是数组元素中最大的有效元素下标
 3             int parent = (index - 1) >>> 1; // 其父结点元素下标
 4             int e = array[parent]; // 取出父结点的元素值
 5             if (key > e) // 和当前元素比较
 6                 break; // 若当前元素大于其父元素,则达到合适的位置,跳出循环
 7             array[index] = e; // 若当前元素小于等于其父元素,设置index下标上的元素为其父结点元素的值e
 8             index = parent; // 索引上移,继续比较
 9         }
10         array[index] = key; // 将新元素放在合适的位置,index的最后更新值
11     }

 

siftDown

heap(1,n) -> heap(2,n)

当x[1..n]是一个堆时,给x[1]分配一个新值得到heap(2,n),然后调用siftDown使得heap(1,n)为真。

该函数将x[1]向下筛选,直到它没有子结点或小于等于它的子结点

下图(从上到下)演示了18在堆中向下筛选,直到到达合适的位置的过程。

x[1]替换为新元素18

 

比其右子结点15大,并与其交换

 

比其左子结点17大,并与其交换,此刻到达合适的位置(比其左子结点19小,没有右子结点)

 

 

代码实现siftDown方法,这里用Java语言实现

它的运行时间和logn成正比,因为堆具有logn层。

 

 1     void siftDown(int index, int key, int[] array, int n) {
 2         if (n > 0) { // n为元素的个数, half = n/2, 表示最后一个拥有子结点的元素结点,index下沉至half的子结点便到底了,所以有while(index < half)
 3             int half = n >>> 1; // 取中间索引作为终结点
 4             while (index < half) {
 5                 int child = (index << 1); // 左子结点索引,【注意】,如果索引0处也用的话,这里应改为,int child = (index << 1) + 1;
 6                 int c = array[child]; // 左子结点的值
 7                 int right = child + 1; // 右子结点索引
 8                 if (right < n && c > array[right]) // 如果右子结点索引没有越界,且左子结点的值大于右子结点的值,则更新c和child分别为其右子结点的值和索引
 9                     c = array[child = right];
10                 if (key <= c) // 若key小于等于c,说明已经找到合适位置,则跳出循环
11                     break;
12                 array[index] = c; // 若当前元素大于其子结点元素,设置index下标上的元素为其父结点元素的值
13                 index = child; // 索引下移,继续比较
14             }
15             array[index] = key; // 将新元素放在合适的位置,index的最后更新值
16         }
17     }

 

siftUp方法和siftDown方法分别对应了插入和删除元素。

 

源码分析

属性

 1     private static final int DEFAULT_INITIAL_CAPACITY = 11; // 默认容量
 2 
 3     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大容量
 4 
 5     private transient Object[] queue; // 数组
 6 
 7     private transient int size; // 大小
 8 
 9     private transient Comparator<? super E> comparator; // 比较器
10 
11     private final ReentrantLock lock; // 可重入锁
12 
13     private final Condition notEmpty; // 条件
14 
15     private transient volatile int allocationSpinLock;
16 
17     private PriorityQueue<E> q; // 这里仅用于序列化

 

构造方法

 1     public PriorityBlockingQueue() { // 构造方法,默认容量,无比较器
 2         this(DEFAULT_INITIAL_CAPACITY, null);
 3     }
 4 
 5     public PriorityBlockingQueue(int initialCapacity) { // 给定容量,无比较器
 6         this(initialCapacity, null);
 7     }
 8 
 9     public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator) { // 给定容量,给定比较器
10         if (initialCapacity < 1)
11             throw new IllegalArgumentException();
12         this.lock = new ReentrantLock();
13         this.notEmpty = lock.newCondition();
14         this.comparator = comparator;
15         this.queue = new Object[initialCapacity];
16     }
17 
18     public PriorityBlockingQueue(Collection<? extends E> c) { // 给定集合
19         this.lock = new ReentrantLock();
20         this.notEmpty = lock.newCondition();
21         boolean heapify = true; // 是否需要调整顺序
22         boolean screen = true; // 是否需要检查空值
23         if (c instanceof SortedSet<?>) { // 有序集合
24             SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
25             this.comparator = (Comparator<? super E>) ss.comparator(); // 获取比较器
26             heapify = false; // 已经有序,无需调整
27         } else if (c instanceof PriorityBlockingQueue<?>) { // 优先级阻塞队列的实例
28             PriorityBlockingQueue<? extends E> pq = (PriorityBlockingQueue<? extends E>) c;
29             this.comparator = (Comparator<? super E>) pq.comparator(); // 获取比较器
30             screen = false; // 不必检查空值
31             if (pq.getClass() == PriorityBlockingQueue.class) // 类型是优先级阻塞队列
32                 heapify = false; // 无需调整顺序
33         }
34         Object[] a = c.toArray(); // 取得其数组,真正保存内容的数据结构
35         int n = a.length; // 数组长度
36         if (a.getClass() != Object[].class) // 若不是Object类型
37             a = Arrays.copyOf(a, n, Object[].class); // 复制一份,使其成为Object类型
38         if (screen && (n == 1 || this.comparator != null)) { // 需要检查null
39             for (int i = 0; i < n; ++i)
40                 if (a[i] == null)
41                     throw new NullPointerException();
42         }
43         this.queue = a;
44         this.size = n;
45         if (heapify)
46             heapify(); // 调整堆,使其符合堆的性质
47     }

 

关键方法

heapify()

 1     private void heapify() {
 2         Object[] array = queue; // 数组
 3         int n = size; // 大小
 4         int half = (n >>> 1) - 1; // 只需要调整前一半元素即可,后面的都是叶子节点,无需调整
 5         Comparator<? super E> cmp = comparator; // 比较器
 6         if (cmp == null) { // 若比较器不存在,则使用元素自身的比较器
 7             for (int i = half; i >= 0; i--)
 8                 siftDownComparable(i, (E) array[i], array, n);
 9         } else { // 否则,使用比较器比较
10             for (int i = half; i >= 0; i--)
11                 siftDownUsingComparator(i, (E) array[i], array, n, cmp);
12         }
13     }

 

siftUp()

 1     private static <T> void siftUpComparable(int k, T x, Object[] array) { // 向上调整,使用元素自身比较器
 2         Comparable<? super T> key = (Comparable<? super T>) x;
 3         while (k > 0) {
 4             int parent = (k - 1) >>> 1; // 索引减半,即是父节点索引,注意,这里,索引是从0开始的,所以是k-1
 5             Object e = array[parent]; // 父节点的值
 6             if (key.compareTo((T) e) >= 0) // 大于其父节点,说明已到合适位置,跳出循环
 7                 break;
 8             array[k] = e; // 当前索引设置父节点的值
 9             k = parent; // 索引上移动
10         }
11         array[k] = key; // key最后放在合适的索引位置
12     }
13 
14     private static <T> void siftUpUsingComparator(int k, T x, Object[] array, Comparator<? super T> cmp) { // 同上,使用给定的比较器
15         while (k > 0) {
16             int parent = (k - 1) >>> 1;
17             Object e = array[parent];
18             if (cmp.compare(x, (T) e) >= 0)
19                 break;
20             array[k] = e;
21             k = parent;
22         }
23         array[k] = x;
24     }

 

siftDown()

 1     private static <T> void siftDownComparable(int k, T x, Object[] array, int n) { // 向下调整,使用元素自身的比较器
 2         if (n > 0) {
 3             Comparable<? super T> key = (Comparable<? super T>) x;
 4             int half = n >>> 1; // 只调整到前一半元素即可,后一半均是叶子节点,无需调整
 5             while (k < half) {
 6                 int child = (k << 1) + 1; // 左子节点,索引从0开始,所以需要+1
 7                 Object c = array[child];
 8                 int right = child + 1; // 右子节点
 9                 if (right < n && ((Comparable<? super T>) c).compareTo((T) array[right]) > 0) // 如果右子结点索引没有越界,且左子结点的值大于右子结点的值,则更新c和child分别为其右子结点的值和索引
10                     c = array[child = right];
11                 if (key.compareTo((T) c) <= 0) // 若key小于等于c,说明已经找到合适位置,则跳出循环
12                     break;
13                 array[k] = c; // 若当前元素大于其子结点元素,设置index下标上的元素为其父结点元素的值
14                 k = child; // 索引下移,继续比较
15             }
16             array[k] = key; // 将新元素放在合适的位置,k的最后更新值
17         }
18     }
19 
20     private static <T> void siftDownUsingComparator(int k, T x, Object[] array, int n, Comparator<? super T> cmp) { // 同上,使用给定的比较器
21         if (n > 0) {
22             int half = n >>> 1;
23             while (k < half) {
24                 int child = (k << 1) + 1;
25                 Object c = array[child];
26                 int right = child + 1;
27                 if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
28                     c = array[child = right];
29                 if (cmp.compare(x, (T) c) <= 0)
30                     break;
31                 array[k] = c;
32                 k = child;
33             }
34             array[k] = x;
35         }
36     }

 

 tryGrow(Object[] array, int oldCap)

 1     private void tryGrow(Object[] array, int oldCap) { // 扩容
 2         lock.unlock(); // 释放offer方法里加的锁
 3         Object[] newArray = null;
 4         if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) { // 自旋锁,50%的概率,因为已经释放锁了,这里用CAS保证只有一个线程能扩容成功
 5             try {
 6                 int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) : // 小于64时,快速增长(oldCap+2),大于等于64,增长缓慢(oldCap/2)
 7                         (oldCap >> 1));
 8                 if (newCap - MAX_ARRAY_SIZE > 0) { // 检查内存溢出
 9                     int minCap = oldCap + 1;
10                     if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
11                         throw new OutOfMemoryError();
12                     newCap = MAX_ARRAY_SIZE;
13                 }
14                 if (newCap > oldCap && queue == array) // 如果没有别的线程抢先扩容,则创建新数组
15                     newArray = new Object[newCap];
16             } finally {
17                 allocationSpinLock = 0;
18             }
19         }
20         if (newArray == null) // 扩容失败,让出CPU时间片
21             Thread.yield();
22         lock.lock(); // 继续往下执行,如果是让出CPU时间片的线程先获得了锁,等其执行结束,会在offer方法里重新释放锁的
23         if (newArray != null && queue == array) { // 加锁,扩容
24             queue = newArray;
25             System.arraycopy(array, 0, newArray, 0, oldCap);
26         }
27     }

如果元素数量 大于等于数组长度,则扩容。

 

 offer(E e)

 1     public boolean offer(E e) {
 2         if (e == null)
 3             throw new NullPointerException();
 4         final ReentrantLock lock = this.lock;
 5         lock.lock();
 6         int n, cap;
 7         Object[] array;
 8         while ((n = size) >= (cap = (array = queue).length)) // 元素数量大于等于数组长度,扩容,无界
 9             tryGrow(array, cap);
10         try {
11             Comparator<? super E> cmp = comparator;
12             if (cmp == null)
13                 siftUpComparable(n, e, array); // 添加元素,自底向上调整
14             else
15                 siftUpUsingComparator(n, e, array, cmp);
16             size = n + 1; // 数量加1
17             notEmpty.signal(); // 通知等待拿元素的线程
18         } finally {
19             lock.unlock();
20         }
21         return true;
22     }

添加元素,自底向上调整。 

 

dequeue()

 1     private E dequeue() {
 2         int n = size - 1;
 3         if (n < 0)
 4             return null;
 5         else {
 6             Object[] array = queue;
 7             E result = (E) array[0]; // 取优先级最高的首元素
 8             E x = (E) array[n];
 9             array[n] = null;
10             Comparator<? super E> cmp = comparator;
11             if (cmp == null)
12                 siftDownComparable(0, x, array, n); // 删除是自顶向下调整
13             else
14                 siftDownUsingComparator(0, x, array, n, cmp);
15             size = n;
16             return result;
17         }
18     }

返回优先级最高的元素,也即是数组首元素。

 

take()

 1     public E take() throws InterruptedException {
 2         final ReentrantLock lock = this.lock;
 3         lock.lockInterruptibly();
 4         E result;
 5         try {
 6             while ( (result = dequeue()) == null)
 7                 notEmpty.await(); // 阻塞,等待被唤醒
 8         } finally {
 9             lock.unlock();
10         }
11         return result;
12     }

调用take方法的线程,遇到队列为空,则会被添加到Condition队列中,等待被唤醒。 

 

行文至此结束。

 

尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_pbq.html

posted @ 2018-05-13 12:48  林城画序  阅读(226)  评论(0编辑  收藏  举报