阻塞佇列 PriorityBlockingQueue 原始碼解析
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;
}
}
相關文章
- Java併發包原始碼學習系列:阻塞佇列實現之PriorityBlockingQueue原始碼解析Java原始碼佇列BloC
- 阻塞佇列 SynchronousQueue 原始碼解析佇列原始碼
- 阻塞佇列 LinkedTransferQueue 原始碼解析佇列原始碼
- 阻塞佇列 DelayQueue 原始碼解析佇列原始碼
- Laravel 佇列原始碼解析Laravel佇列原始碼
- Java併發包原始碼學習系列:阻塞佇列實現之ArrayBlockingQueue原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之LinkedBlockingQueue原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之SynchronousQueue原始碼解析Java原始碼佇列
- Java併發包原始碼學習系列:阻塞佇列實現之LinkedBlockingDeque原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之LinkedTransferQueue原始碼解析Java原始碼佇列
- Java併發包原始碼學習系列:阻塞佇列實現之DelayQueue原始碼解析Java原始碼佇列
- 佇列、阻塞佇列佇列
- 阻塞佇列一——java中的阻塞佇列佇列Java
- 探討阻塞佇列和執行緒池原始碼佇列執行緒原始碼
- 原始碼剖析ThreadPoolExecutor執行緒池及阻塞佇列原始碼thread執行緒佇列
- Java併發包原始碼學習系列:基於CAS非阻塞併發佇列ConcurrentLinkedQueue原始碼解析Java原始碼佇列
- 從原始碼解析-Android資料結構之單向阻塞佇列LinkedBlockingQueue的使用原始碼Android資料結構佇列BloC
- 迴歸Java基礎:LinkedBlockingQueue阻塞佇列解析JavaBloC佇列
- 阻塞佇列 BlockingQueue佇列BloC
- 阻塞佇列--LinkedBlockingQueue佇列BloC
- 死磕阻塞佇列佇列
- [原始碼解析] 訊息佇列 Kombu 之 基本架構原始碼佇列架構
- 多執行緒程式設計-分析阻塞佇列的原始碼實現執行緒程式設計佇列原始碼
- 延遲阻塞佇列 DelayQueue佇列
- 阻塞佇列BlockingQueue(三)--DelayQueue佇列BloC
- Java中的阻塞佇列Java佇列
- 阻塞佇列——四組API佇列API
- Java JUC PriorityBlockingQueue解析JavaBloC
- 原始碼解析Synchronous Queue 這種特立獨行的佇列原始碼佇列
- AQS原始碼深入分析之條件佇列-你知道Java中的阻塞佇列是如何實現的嗎?AQS原始碼佇列Java
- 聊聊併發(四)——阻塞佇列佇列
- Java中常用的七個阻塞佇列第二篇DelayQueue原始碼介紹Java佇列原始碼
- Java併發包原始碼學習系列:阻塞佇列BlockingQueue及實現原理分析Java原始碼佇列BloC
- laravel原始碼分析-佇列QueueLaravel原始碼佇列
- [原始碼解析] 分散式任務佇列 Celery 之啟動 Consumer原始碼分散式佇列
- 阻塞佇列 BlockingQueue(二)--ArrayBlockingQueue與LinkedBlockingQueue佇列BloC
- Java併發系列 — 阻塞佇列(BlockingQueue)Java佇列BloC
- Java併發——阻塞佇列集(上)Java佇列