阻塞佇列 PriorityBlockingQueue 原始碼解析

槑!發表於2020-10-16

PriorityBlockingQueue

  • 一個支援優先順序無界阻塞佇列。預設情況下元素採用自然順序升序排序,當然我們也可以通過建構函式來指定Comparator來對元素進行排序。需要注意的是PriorityBlockingQueue不能保證同優先順序元素的順序。
  • 底層實現是陣列,採用的堆的思想(最小堆),保證陣列第零個元素是最小的元素,但是注意,其增加和刪除元素的調整方法並非是堆排的調整方法,即不用滿足最小堆的堆排所要求的左子節點小於右子節點,只用滿足根元素是最小的元素。併發安全也是通過 Lock 保證,但擴容那裡也使用了 CAS。
屬性
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable
    
    // 預設容量
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // 最大容量
    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;
	// 只有非空等待,因為二叉堆是無界的,會擴容
    private final Condition notEmpty;

    //
    private transient volatile int allocationSpinLock;

    // 優先佇列:主要用於序列化,這是為了相容之前的版本。只有在序列化和反序列化才非空
    private PriorityQueue<E> q;

入隊
  • 只介紹put,其餘add、offer 略
public void put(E e) {
    offer(e); // never need to block
}

public boolean offer(E e) {
    // 不能為null
    if (e == null)
        throw new NullPointerException();
   
    final ReentrantLock lock = this.lock;
    // 獲取鎖
  	ck.lock();
    
    int n, cap;
    Object[] array;
    
    // 當 size > queue.lenth
    // 放在 while 迴圈裡是因為擴容時用了 cas 鎖,但是沒有迴圈,所以在這裡進行了自旋
    while ((n = size) >= (cap = (array = queue).length))
      // 擴容
      tryGrow(array, cap);
    
    try {
        Comparator<? super E> cmp = comparator;
        // 根據比較器是否為null,做不同的處理
        if (cmp == null)
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        // 增加 size
        size = n + 1;
        
        // 喚醒正在等待的消費者執行緒
        notEmpty.signal();
    } finally {
        // 釋放鎖
        lock.unlock();
    }
    return true;
}

/**
 * 擴容
 */
private void tryGrow(Object[] array, int oldCap) {
	// 擴容操作使用自旋,不需要鎖主鎖,釋放(因為下面只是建立一個新陣列,並不會操作原陣列)
    lock.unlock();      
    
    Object[] newArray = null;
    
    // CAS 鎖(原理就是 cas 為另一個值後,如果不再結束時改為原先的值,那麼其他其他執行緒就一直無法 cas 成功)
    if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) {
        try {
            // 新容量 
            // oldCpa < 64: newCap = 2 OldCap + 2、
            // oldCap >= 64: newCap = 1.5 oldCap
            int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) :  (oldCap >> 1));

            // 超過
            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                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;     // 擴容後allocationSpinLock = 0 還原為原先的值(代表釋放了自旋鎖)
        }
    }
    
    // 到這裡如果是本執行緒擴容newArray肯定是不為null,為null就是其他執行緒在處理擴容,那就讓給別的執行緒處理
    if (newArray == null)
        Thread.yield();
    
    // 主鎖獲取鎖,因為下面要進行老陣列的拷貝了,所以要獲取鎖
    lock.lock();
    
    // 陣列複製
    if (newArray != null && queue == array) {
        queue = newArray;
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

/**
 * 當比較器不為null時,採用所指定的比較器
 *
 * k:陣列裡面元素個數
 * x:要插入的元素
 * array:要插入的陣列
 */
private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    
    // 尋找 x 插入的位置(其思想是,假定 x 插入在陣列最後一個位置,然後一直跟父節點比較,如果 >= 父節點那麼就可以作其子節點,
    // 否則就和父節點交換,然後繼續再往上找)
    while (k > 0) {
        // 父級節點
        int parent = (k - 1) >>> 1;
        Object e = array[parent];

        // key >= parent 那麼就可以做 parent 的子節點,安插在這個位置即可
        if (key.compareTo((T) e) >= 0)
            break;
        // key < parant 就替換接著往上比
        array[k] = e;
        k = parent;
    }
    array[k] = key;
}

/**
 * 當比較器不為null時,採用所指定的比較器
 */
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;
}
出隊
public E poll() {
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         return dequeue();
     } finally {
         lock.unlock();
     }
 }
 
private E dequeue() {
     // 沒有元素 返回null
     int n = size - 1;
     if (n < 0)
         return null;
     else {
         Object[] array = queue;
         // 出隊元素(第 0 號位置的最小元素)
         E result = (E) array[0];
         
         // 最後一個元素(也就是假定放到被刪除位置的元素)
         E x = (E) array[n];
         array[n] = null;
         
         // 根據比較器釋放為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;
     }
 }

/**
 * 如果比較器為null,則呼叫siftDownComparable來進行自然排序處理
 *
 * k:被刪除節點位置索引
 * x:陣列最後一個元素(此位置已經是 null 了,因為刪了一個前面的元素,所以會找到一個新位置來放這個元素)
 * array:要調整的陣列
 * n:陣列元素個數(不包括 x 所在的最後一個位置了)
 */
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;
        
        // 思想是假定把 x 放到了被刪除的位置,然後獲得其左右節點節點中最小的值,如果 x 小於這個值,那麼 x 就可以放在這,
        // 否則交換左右子節點中最小的交換位置,然後接著再和子節點比較,直到找到位置
        while (k < half) {
            int child = (k << 1) + 1;       // 待調整位置左節點位置
            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;
            
            //如果key不是最小,那就取左右節點小的那個放到調整位置,然後小的那個節點位置開始再繼續調整
            array[k] = c;
            k = child;
        }
        array[k] = key;
    }
}

/**
 * 如果指定了比較器,則採用比較器來進行調整
 */
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;
    }
}

相關文章