• 【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

  • 相关阅读:
    [solr]
    [solr]
    [Linux] CentOS 加入开机启动
    [Linux] VirtualBox
    [Eclipse]
    [JBoss]
    [solr]
    [solr]
    [solr]
    [solr]
  • 原文地址:https://www.cnblogs.com/aniao/p/aniao_pbq.html
Copyright © 2020-2023  润新知