30分鐘帶你瞭解阻塞佇列所有內容,再也不怕面試官刁難你了!(上)
目錄
- 1、概述
- 2、BlockingQueue 原始碼解析
- 3、ArrayBlockingQueue 原始碼解析
- 4、LinkedBlockingQueue 原始碼解析
- 5、PriorityBlockingQueue 原始碼解析
嘔心瀝血,耗費一週的時間來看原始碼,這麼優秀的博主你難道不關注一下?
1、概述
今天在整理執行緒池的相關內容時,發現許多面試官對於阻塞佇列這塊內容問的還是挺挺深入的。事實上也是,公司招人的時候,總不會去招那些只會用,但是不知道為什麼的人吧。而且阻塞佇列是執行緒池的核心內容,因此我們將這塊內容給搞懂了,這樣才能在開發中遊刃有餘,在面試時鎮定自若。
然而在找尋相關部落格的時候,發現除了大部分部落格千篇一律的copy之外,一些原創部落格出現一些內容不全、不明原理、甚至內容重複的現象,這真的是對我們求知若渴的孩子的打擊。因此,我從 IDEA 中的類圖功能一個個找過去,給大家解讀原始碼,所以,這裡,以我為準。
先給大家看一下BlockingQueue
的相關類的圖。(???為什麼是BlockingQueue
?兄弟快去看看執行緒池建構函式的引數!)
2、BlockingQueue 原始碼解析
BlockingQueue
是一個介面,它繼承了Queue
介面。然後下面是它的方法:
// 這裡我們看到 BlockingQueue 是一個泛型介面,但是我們線上程池中用的是 Runnable 介面也就是執行緒可執行動作。這裡瞭解一下
public interface BlockingQueue<E> extends Queue<E> {
/**
* Q:好的,問題來了,我們看到下面三個方法都是新增元素,那麼他們有什麼不同嘛?
* A:有的,我們看上面,BlockingQueue 實現了 Queue 介面,Queue 介面又實現了 Collection 介面。其實這三個方法分別屬於不同介面中的定義。
* add 方法來源於 Collection 介面,並且在 BlockingQueue 中定義當大小不足時,會丟擲 IllegalStateException;
* offer 方法來源於 Queue 介面,並且在 BlockingQueue 中定義當大小不足時,會返回 false 而不是拋錯;
* put 方法是 BlockingQueue 介面自己定義的,並且在 BlockingQueue 中定義當大小不足時會掛起,直到有足夠的大小。允許被打斷,打斷會丟擲 InterruptedException。
*/
// 新增元素
boolean add(E e);
// 新增元素
boolean offer(E e);
// 新增元素
void put(E e) throws InterruptedException;
// 新增元素,並且有一個時間。超過時間就放棄
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
// 把第一個元素取走並刪除,如果沒有的話就掛起直到有,打斷會丟擲 InterruptedException
E take() throws InterruptedException;
// 也是拿元素,超時就放棄,打斷會丟擲 InterruptedException
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
// 查詢剩餘容量
int remainingCapacity();
// 刪除元素。這裡為什麼不用 E 而用 Object 呢?我覺得有兩點吧:1、限定了元素為非基礎型別;2、在元素比較時需要用到 equals 方法,在註釋中有用到
boolean remove(Object o);
// 查詢元素,也是用 Object
public boolean contains(Object o);
// 將當前所有可用元素放到 C 中,需要子類自己實現。原註釋提出,drainTo 方法比迭代呼叫 poll 元素要好,因為迭代過程出現異常可能會導致元素的丟失
int drainTo(Collection<? super E> c);
// 將指定大小個元素放到 C 中
int drainTo(Collection<? super E> c, int maxElements);
}
3、ArrayBlockingQueue 原始碼解析
3-1、ArrayBlockingQueue 概述
ArrayBlockingQueue
是一個有界的BlockingQueue
,內部儲存使用陣列。ArrayBlockingQueue
提供先進先出的機制,提供公平鎖和非公平鎖的獲取(那麼有同學可能要問了,既然保證了先進先出那不就肯定是公平的嘛?別急,這個問題我們在下面原始碼中進行回答)。
3-2、ArrayBlockingQueue 原始碼
我會根據使用邏輯將原始碼順序進行調換,不要以出現順序當做原始碼中的真實位置。
3-2-1、成員變數
首先是一些成員變數:
// 儲存元素的陣列。可以看到是 final 修飾的,意味著一旦 ArrayBlockingQueue 建立之後,大小不能再改變了
final Object[] items;
// 下一個獲取元素的索引值。為什麼要這樣子呢?因為 ArrayBlockingQueue 規定了獲取肯定是從儲存最久的元素開始的,如果是連結串列的話好辦,直接改節點就行了,但是陣列的話我們就必須用頭尾指標表示當前頭在哪裡,尾在哪裡,否則每次拿元素都得進行陣列的重構,特別浪費時間。其實這兩個索引值是相當於用陣列來完成連結串列的表示的妥協方法
int takeIndex;
// 同上
int putIndex;
// 用於記錄當前元素個數
int count;
/*
* 下面是一些併發控制元件
*/
// 一個可重入鎖來保證併發過程中 ArrayBlockingQueue 的正確性。final 保證了這個鎖的安全性
final ReentrantLock lock;
// 一個 condition
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
// 一個內部類迭代器
transient Itrs itrs = null;
3-2-2、構造方法
構造方法:
// 呼叫下面的構造方法,預設使用非公平鎖
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
// 懶載入。這邊就可以看出上面提出的問題了。既然 ArrayBlockingQueue 保證了先進先出那不就肯定是公平的嘛,為什麼要說提供了公平和非公平呢?我們雖然保證了阻塞佇列中獲取執行緒池中執行緒資源的時候是公平的,但是我們沒有保證入阻塞佇列的時候是公平的,我們還要保證獲取 ArrayBlockingQueue 的可重入鎖的等待佇列的公平性(想要知道這個,AQS 相關內容瞭解一下)
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
// 在懶載入的同時還把 c 中的內容放到這個阻塞佇列中來
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
3-2-3、陣列輔助方法
然後是三個陣列操作的輔助方法:
// 返回 n-1 的值。當 n==0 時,返回阻塞佇列的長度-1
final int dec(int i) {
return ((i == 0) ? items.length : i) - 1;
}
// 返回指定下標的元素
@SuppressWarnings("unchecked")
final E itemAt(int i) {
return (E) items[i];
}
// 檢查元素是否為 null
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
3-2-4、佇列方法
接下來講佇列方法->進隊、出隊方法的實現。由於後面的程式碼會用到,但是後面程式碼太多,因此提前講。
// 入隊。使用了 condition 是為了滿足之前 BlockingQueue 介面中提出的”佇列已滿就掛起直到有空閒空間“的功能,這裡因為加了一個元素,因此將 notEmpty(即等待取出)的那些執行緒喚醒
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
// 增加元素後將尾指標的位置進行合理修改
if (++putIndex == items.length)
putIndex = 0;
count++;
// 喚醒那些準備出隊的執行緒
notEmpty.signal();
}
// 出隊。使用 condition 的原因跟上面相同。和上面形成互補
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
// 元素出隊後將頭指標的位置進行合理修改
if (++takeIndex == items.length)
takeIndex = 0;
count--;
// 在迭代器中將元素出隊
if (itrs != null)
itrs.elementDequeued();
// 喚醒那些準備入隊的執行緒
notFull.signal();
return x;
}
// 刪除指定下標的元素(只有持有鎖的執行緒才能這麼做)
void removeAt(final int removeIndex) {
final Object[] items = this.items;
// 如果下標值和頭指標相等,直接執行出隊邏輯
if (removeIndex == takeIndex) {
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
// 下標值和頭指標的值不同,那麼需要將後面的元素往前搬
} else {
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
// 在迭代器中將元素出隊
if (itrs != null)
itrs.removedAt(removeIndex);
}
// 喚醒那些準備入隊的執行緒
notFull.signal();
}
3-2-5、介面方法實現
然後是 BlockingQueue
、Queue
和Collection
介面中方法的實現:
// 新增元素。add 方法沒有加鎖?為什麼?因為 super.add 呼叫的是 offer 方法。。。
public boolean add(E e) {
return super.add(e);
}
// 新增元素。加鎖之後判斷佇列是否滿了,滿了話返回false,否則執行入隊方法
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
// 新增元素。不同的是,這邊會根據情況掛起
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 用 lock.lockInterruptibly() 而不用 lock,表明該方法允許被其他執行緒中斷
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
// 新增元素。超時則放棄
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
// 這個是設定等待時間,超時就會中斷
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
// 獲取元素,沒有元素則返回 null,有則執行出隊方法
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
// 獲取元素,沒有則等待。直到被入隊方法喚醒
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
// 獲取元素,沒有則等待,超時則中斷
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
// 這邊也是獲取元素,但是元素不出隊,因此不刪除,所以使用 itemAt 而不是 dequeue
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
// 返回大小。這邊也 lock 了,嚴謹!
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
// 返回陣列剩餘空間
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return items.length - count;
} finally {
lock.unlock();
}
}
// 刪除元素
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
// 這邊使用 equals 方法進行判定,所以上面使用 Object 而不是泛型
if (o.equals(items[i])) {
// 上面獲取了鎖,因此可以呼叫 removeAt 方法
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
// 檢查元素是否存在
public boolean contains(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
// 看的出來,其實是遍歷
do {
if (o.equals(items[i]))
return true;
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
// 這邊返回 Object陣列。但不是直接返回陣列,而是呼叫 System.arraycopy 進行復制
public Object[] toArray() {
Object[] a;
final ReentrantLock lock = this.lock;
lock.lock();
try {
final int count = this.count;
a = new Object[count];
int n = items.length - takeIndex;
// 因為有頭尾指標的關係,因此需要判斷。可以看的出來,這邊複製之後的依然是有序的!
if (count <= n)
System.arraycopy(items, takeIndex, a, 0, count);
else {
System.arraycopy(items, takeIndex, a, 0, n);
System.arraycopy(items, 0, a, n, count - n);
}
} finally {
lock.unlock();
}
return a;
}
// 這邊返回泛型陣列,也是使用 System.arraycopy 進行復制,不同的是這邊使用了反射來進行泛型陣列的初始化
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
final int count = this.count;
final int len = a.length;
if (len < count)
// 不像 Object 那麼方便,需要使用反射來進行陣列的初始化
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), count);
int n = items.length - takeIndex;
if (count <= n)
System.arraycopy(items, takeIndex, a, 0, count);
else {
System.arraycopy(items, takeIndex, a, 0, n);
System.arraycopy(items, 0, a, n, count - n);
}
if (len > count)
a[count] = null;
} finally {
lock.unlock();
}
return a;
}
// toString 方法,不多說
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k == 0)
return "[]";
final Object[] items = this.items;
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = takeIndex; ; ) {
Object e = items[i];
sb.append(e == this ? "(this Collection)" : e);
if (--k == 0)
return sb.append(']').toString();
sb.append(',').append(' ');
if (++i == items.length)
i = 0;
}
} finally {
lock.unlock();
}
}
// 清空佇列。因為有加鎖,因此可以認為是原子操作
public void clear() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
// 迴圈將陣列清空。因為原陣列是 final 的,因此不能直接 new 一個這麼簡單
do {
items[i] = null;
if (++i == items.length)
i = 0;
} while (i != putIndex);
takeIndex = putIndex;
count = 0;
// 順便將迭代器中的也清空
if (itrs != null)
itrs.queueIsEmpty();
// 將阻塞佇列中想要新增元素的執行緒全部喚醒
for (; k > 0 && lock.hasWaiters(notFull); k--)
notFull.signal();
}
} finally {
lock.unlock();
}
}
// 呼叫下面 drainTo 方法
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
// 使用鎖來保證元素轉移的時候不會出問題
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = Math.min(maxElements, count);
int take = takeIndex;
int i = 0;
try {
while (i < n) {
@SuppressWarnings("unchecked")
E x = (E) items[take];
c.add(x);
items[take] = null;
if (++take == items.length)
take = 0;
i++;
}
return n;
} finally {
// 如果出錯,那麼搬過去的內容也不轉回來
if (i > 0) {
count -= i;
takeIndex = take;
if (itrs != null) {
if (count == 0)
itrs.queueIsEmpty();
else if (i > take)
itrs.takeIndexWrapped();
}
for (; i > 0 && lock.hasWaiters(notFull); i--)
notFull.signal();
}
}
} finally {
lock.unlock();
}
}
3-2-6、內部類
接下來是兩個內部類,這兩個內部類提供了更多複雜的方法,但是跟我們阻塞佇列的主邏輯(BlockingQueue)沒有關係,因此這邊擱置不談,大家有興趣可以自己去看,以後有機會我再補上來。
ArrayBlockingQueue 總結
上面我們看完了 ArrayBlockingQueue
的原始碼,相信大家都已經看過了。在1.8中,ArrayBlockingQueue
一共也只有 1444 行原始碼,去除註釋和無關核心的內容不談也就寥寥幾百行,所以看原始碼 jdk 原始碼真的不難,只要肯堅持。看到這邊大家也忘得差不多了,這邊再總結一下:
- ArrayBlockingQueue 是一個有界佇列,建立之後大小不能修改;
- ArrayBlockingQueue 使用了頭尾指標來構建佇列,而不是連結串列的方式;
- ArrayBlockingQueue 提供公平和非公平的機制,來讓大家去向執行緒池中的資源,這種機制取決於佇列的 FIFO 和可重入鎖的公平機制的配合;
- 內部使用了 condition 來喚醒或等待。
4、LinkedBlockingQueue 原始碼解析
4-1、LinkedBlockingQueue 概述
LinkedBlockingQueue
是一個無界的阻塞佇列,內部採用Node
連結串列來實現佇列,實現了FIFO的特性。不同於ArrayBlockingQueue
,LinkedBlockingQueue
只提供了非公平的搶鎖機制,因此入隊先後是不公平的(這點在程式碼中可以體現)。
4-2、LinkedBlockingQueue 原始碼
4-2-1、Node 類
使用靜態內部類Node
來作為儲存結構:
// 可以看到節點只有'元素'和'next'兩個成員,因此形成的連結串列是單向的
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
4-2-2、成員變數
接下來是一些成員變數(這裡為了統一稱呼我把 final 型別的也成為'變數')
// final 修飾的容量,因此可以看出容量是固定的。那就得看初始值是啥了
private final int capacity;
// final 和原子型別修飾的元素個數。使用原子型別來保證多執行緒操作的安全性。注意:使用 final 並不意味著值不能修改,只是該引用指向的物件地址不能修改而已,真正的值是 AtomicInteger 裡的`private volatile int value;`,可以通過`setValue()`方法修改
private final AtomicInteger count = new AtomicInteger();
// 頭結點
transient Node<E> head;
// 尾結點
private transient Node<E> last;
/**
* 注意這裡,LinkedBlockingQueue 將入隊鎖出隊鎖分離,提高了佇列的操作速度,因此能夠提高併發量
*/
// 出隊鎖,使用餓漢,並用 final 修飾保證鎖不被替換
private final ReentrantLock takeLock = new ReentrantLock();
// 非空條件物件,用來掛起和喚醒獲取元素的執行緒
private final Condition notEmpty = takeLock.newCondition();
// 出隊鎖
private final ReentrantLock putLock = new ReentrantLock();
// 沒滿條件物件,用來掛起和喚醒新增元素的執行緒
private final Condition notFull = putLock.newCondition();
4-2-3、構造方法
LinkedBlockingQueue
的建構函式跟ArrayBlockingQueue
差不多吧,只有一些細節地方需要注意
// 呼叫下面的構造方法,注意預設的佇列長度設定為 Integer.MAX_VALUE,因此 LinkedBlockingQueue 預設是無界佇列,如果這麼多的話容易導致 OOM,因此建議自己手動設定一個值,設定成有界佇列。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
// 這邊判斷了 capacity 引數範圍並賦值,並且構建了哨兵節點,讓頭尾指標都指向了該哨兵節點
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
// 這邊賦值的時候順便將 c 的內容入隊了
public LinkedBlockingQueue(Collection<? extends E> c) {
// 這邊容量設定成了 Integer.MAX_VALUE
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
// 入隊獲取入隊鎖
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
// 執行入隊操作
enqueue(new Node<E>(e));
// 已入隊元素個數 ++
++n;
}
// 將加進來的值賦值給了 count 裡的 value
// 這邊這麼寫應該是為了提高效率,如果直接使用原子型別的 CAS 進行遞增,還要直接跟記憶體打交道,顯然降低了併發的效率
// 但是有個問題,如果上面出錯了,count 的值是不會賦值的,後面如果進行判斷的時候會出問題的。我相信原始碼編寫者肯定想到了這個問題,後面應該不會直接利用 count 進行判斷吧。1.8裡有這個bug,pick一下!!!
count.set(n);
} finally {
putLock.unlock();
}
}
4-2-4、condition 方法
實現了兩個方法,封裝了 condition 的signal
方法
// 喚醒等待獲取元素的執行緒。只能被 offer、put 方法呼叫。需要獲取出隊鎖
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
// 喚醒等待新增元素的執行緒。只能被 take、poll 方法呼叫。需要獲取入隊鎖
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
4-2-5、佇列(連結串列)方法
實現了佇列的入隊、出隊方法。需要用到頭指標和尾指標
// 入隊。從放到末尾
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
// 出隊。出隊的概念比較麻煩,下面程式碼可以通過輔助畫一張圖來理解
private E dequeue() {
// head 節點是哨兵節點,因此出隊永遠從 head.next 出隊
Node<E> h = head;
// 將要出隊的元素賦值給 first
Node<E> first = h.next;
// 將原來的哨兵節點的 next 指向自己,讓 GC回收(這裡為什麼不直接將其指向 next.next,然後回收出隊的節點呢?)
h.next = h; // help GC
// 後面三行是將要出隊的元素賦值給 x,並且將這個節點設定成哨兵節點
head = first;
E x = first.item;
first.item = null;
// 返回 x
return x;
}
4-2-6、鎖方法
這邊對鎖方法進行了封裝,提供了全鎖和全解鎖兩個方法
// 同時獲取出隊鎖和入隊鎖,防止讀寫操作的進行(我覺得可能是用於 drainTo 方法裡吧)
void fullyLock() {
putLock.lock();
takeLock.lock();
}
// 同時釋放出對鎖、入隊鎖
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
4-2-7、介面方法實現
然後是 BlockingQueue
、Queue
和Collection
介面中方法的實現:
// 返回當前元素個數(由於之前建構函式那邊的bug,這邊是不是其實還要進行處理。。。至少確認一遍吧)
public int size() {
return count.get();
}
// 返回剩餘空閒容量
public int remainingCapacity() {
return capacity - count.get();
}
// 入隊操作,如果滿了,那就掛起
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
// 佇列滿了就掛起
while (count.get() == capacity) {
notFull.await();
}
// 執行入隊操作
enqueue(node);
// 使用原子操作保證執行緒安全,因為這邊只獲取了入隊鎖,沒有獲出隊鎖。此時 c 是 count ++ 之前的值(為什麼?看一下原子操作)。
c = count.getAndIncrement();
// 如果佇列沒滿就喚醒那些等著入隊的操作
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 這邊表明了已經有且一個元素入隊了,喚醒等待獲取的執行緒。
// 但是,這邊是不是也有問題?這邊沒有獲得出隊鎖,萬一其他執行緒已經讀取了,實際上佇列是空的???其實麼有!哈哈,我們看其他出隊被掛起的地方都是在 take() 方法中,而被喚醒之後,那個執行緒是在一個 while 中的!!!此時他又要去判斷此時元素個數,是原子操作因此顯然沒有併發問題,所以他又會 wait!!!呵,騙我到這裡來,100塊也不給我???
if (c == 0)
signalNotEmpty();
}
// 入隊。如果滿了就等待一段時間,時間超過就放棄操作
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
}
// 入隊,滿了就返回 false,不掛起,就是剛。
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
// 沒滿,獲的入隊鎖
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
// 出隊,佇列為空就掛起
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
// 出隊,佇列為空就等一段時間,超時就放棄
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
// 出隊,佇列為空就返回false
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
// 這邊只是返回第一個節點的元素,不進行佇列的修改
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
// 把節點 p 的連結清除,其實就是刪掉 p 節點
void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
if (count.getAndDecrement() == capacity)
notFull.signal();
}
// 刪除元素 o。
public boolean remove(Object o) {
if (o == null) return false;
fullyLock();
try {
// 利用 for 迴圈找到元素 o 的節點
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
// 找到節點後呼叫 unlink 方法刪除節點 p 和他的連結
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
// 檢查是否存在元素 o。
public boolean contains(Object o) {
if (o == null) return false;
fullyLock();
try {
// 遍歷 + 比較
for (Node<E> p = head.next; p != null; p = p.next)
if (o.equals(p.item))
return true;
return false;
} finally {
fullyUnlock();
}
}
// 將元素遷移到 c 中。呼叫了下面的方法
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
// 將元素遷移到 c 中,最多遷移 maxElements 個
public int drainTo(Collection<? super E> c, int ) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
boolean signalNotFull = false;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
int n = Math.min(maxElements, count.get());
// count.get provides visibility to first n Nodes
Node<E> h = head;
int i = 0;
try {
while (i < n) {
Node<E> p = h.next;
c.add(p.item);
// 遷移之後需要刪掉佇列中的節點
p.item = null;
h.next = h;
h = p;
++i;
}
return n;
} finally {
// Restore invariants even if c.add() threw
if (i > 0) {
// assert h.item == null;
head = h;
signalNotFull = (count.getAndAdd(-i) == capacity);
}
}
} finally {
takeLock.unlock();
if (signalNotFull)
signalNotFull();
}
}
4-2-8、其他內部類和方法
後面是 toString
、toArray
方法、序列化反序列化方法以及迭代器內部類和拆分器內部類。由於這些內容和阻塞佇列核心不怎麼相關,就擱置不講了,以後有機會再補。
4-3、LinkedBlockingQueue 總結
看完了原始碼,原始碼有點長,過了一遍也容易忘,這邊再來總結一下:
LinkedBlockingQueue
是一個預設無界(推薦呼叫有界的構造方法進行構造,避免出現 OOM 的情況)的阻塞佇列,一旦建立大小不能修改;LinkedBlockingQueue
內部使用Node
儲存元素實現佇列,單連結串列,有頭尾指標(頭指標專注出隊、尾指標專注出隊);LinkedBlockingQueue
將入隊鎖和出隊鎖分離,極大地提高了併發的效率。但是由於這兩個鎖都是 final 修飾的並且使用餓漢載入,因此預設是使用ReentrantLock
的非公平策略,因此雖然保證了阻塞佇列的先進先出,但是入隊的過程是非公平的;- 內部使用 Condition 機制是程式進行休眠和喚醒;
- 建構函式中有個小bug(面試的時候可以提出來,說不定可以加分。而且在修改節點以後判斷長度喚醒執行緒那裡也可以進行優化,避免喚醒執行緒又讓其進行睡眠浪費資源)
5、PriorityBlockingQueue 原始碼解析
5-1、PriorityBlockingQueue 概述
PriorityBlockingQueue
是一個基於陣列+堆的預設無界阻塞佇列,並且內部通過堆的結構實現了優先佇列的特性。具體如何實現,還看原始碼。
5-2、PriorityBlockingQueue 原始碼
5-2-1、成員屬性
首先是PriorityBlockingQueue
的成員屬性:
// 預設初始化容量,指的是入隊元素的個數
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//最大陣列長度指的是最大的元素容量,為了避免虛擬機器可能會保留一些空間,所以用了 Integer.MAX_VALUE - 8
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;
// 可重入鎖用於執行緒安全,final 修飾因此不可改變。在構造方法中沒有給我們選公平鎖或者非公平鎖,因此預設是非公平鎖。因此可得入隊是不公平的
private final ReentrantLock lock;
// condition 用於睡眠或喚醒執行緒。這裡只有一個非空的條件,因此可以猜測出該佇列是可以無限擴容的(記憶體允許)
private final Condition notEmpty;
// 自旋鎖的狀態,使用CAS去獲取值。用於擴容
private transient volatile int allocationSpinLock;
// 一個優先佇列,只用於序列化和反序列化
private PriorityQueue<E> q;
5-2-2、構造方法
然後是4個構造方法:
// 構造預設初始大小的阻塞佇列
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
// 構造指定初始大小的阻塞佇列
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
// 構造指定初始大小的阻塞佇列,並傳入比較器,初始化重入鎖、condition等
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
// 建立一個阻塞佇列,並將所給的集合中的元素放到佇列中,並把大小設為所給集合的大小。
// 在此過程中沒有鎖的獲得,因此可能會因為併發出錯
public PriorityBlockingQueue(Collection<? extends E> c) {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
// 是否要將整個堆重新排
boolean heapify = true; // true if not known to be in heap order
// 是否要檢查元素是不是為 null
boolean screen = true; // true if must screen for nulls
// 如果本身就是一個有序的set
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
// 把 c 的比較器傳給該阻塞佇列
this.comparator = (Comparator<? super E>) ss.comparator();
// 由於 set 有序,因此不需要整個堆再排序
heapify = false;
}
// 如果本身就是一個優先阻塞佇列
else if (c instanceof PriorityBlockingQueue<?>) {
PriorityBlockingQueue<? extends E> pq =
(PriorityBlockingQueue<? extends E>) c;
// 把 c 的比較器傳給該阻塞佇列
this.comparator = (Comparator<? super E>) pq.comparator();
// 不需要檢查元素是否為 null
screen = false;
// 如果 class 完全相等,那就不需要整個堆再排。(為什麼子類還需要再排呢?為了防止子類中重寫的排序方法與正常的排序矛盾?)
if (pq.getClass() == PriorityBlockingQueue.class) // exact match
heapify = false;
}
Object[] a = c.toArray();
// 將 c 中元素個數值賦給 n,後面再賦給 size
int n = a.length;
// 如果 c.toArray() 返回的格式有問題,那就使用 Arrays.copy 進行拷貝
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, n, Object[].class);
// 驗證元素是否為 null,'與'後面的判斷條件的條件很有意思,可以思考一下為什麼
if (screen && (n == 1 || this.comparator != null)) {
for (int i = 0; i < n; ++i)
if (a[i] == null)
throw new NullPointerException();
}
// 賦值
this.queue = a;
this.size = n;
// 堆重排
if (heapify)
heapify();
}
5-2-3、擴容機制
下面是PriorityBlockingQueue
的擴容機制:
private void tryGrow(Object[] array, int oldCap) {
// 先釋放鎖,在分配好空間後再獲得鎖進行復制
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
// 此時擴容自旋鎖為 0,表明當前沒有其他執行緒在進行擴容,那麼繼續執行擴容機制
if (allocationSpinLock == 0 &&
// 這個 allocationSpinLockOffset 是類靜態變數,後面才加入的。由於使用的是 sun 公司的 unsafe,因此原始碼沒法看,大概就是檢視這個欄位是不是期望的 0 然後改成1(返回true),否則返回 false
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
// 如果原容量 < 64,那麼新容量變成 2 * old + 2,否則變成 1.5 * old。可以看得出來,當原容量越小,增長越快
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(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;
}
// 如果擴容了,並且陣列沒改變(這裡是什麼意思呢?首先,這個擴容機制是在 offer 中用的,傳的 array 就是原 queue 的地址。這裡沒改變說明沒有其他執行緒進行擴容,只有擴容了才會把地址進行修改),那就建立新容量大小的陣列
if (newCap > oldCap && queue == array)
newArray = new Object[newCap];
} finally {
// 將擴容鎖變成釋放狀態(雖然這邊是 volatile,但是還是會有併發安全的問題啊。。。作者咋想的)
allocationSpinLock = 0;
}
}
// 說明上面的迴圈沒進,說明有執行緒在擴容,那麼放棄 CPU
if (newArray == null) // back off if another thread is allocating
Thread.yield();
// 獲得鎖並複製舊元素
lock.lock();
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
5-2-4、佇列方法
這邊雖然說是佇列操作,但其實由於內部變成了堆的關係,已經不是傳統意義上的入隊出隊的,所以雖然有dequeue
方法,但是enqueue
變成了堆的'上浮'和'下沉'。
/**
* 下面的方法都是基於已經獲取鎖的情況下才能呼叫的。上浮操作用於增加元素後堆重排,下沉操作用於元素出隊以後的堆重排
*/
// 出隊操作
private E dequeue() {
// 獲得最後一個元素的下標
int n = size - 1;
// 如果沒有元素,就返回 null
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)
// 從頂部開始向下比較。如果比較器為空,那麼使用預設的方法(obj.compareTo)進行比較
siftDownComparable(0, x, array, n);
else
// 從頂部開始向下比較。如果比較器不為空,那麼使用比較器的比較方法(cmp.compareTo)進行比較
siftDownUsingComparator(0, x, array, n, cmp);
size = n;
return result;
}
}
// 使用預設的比較器,從堆底部開始向上進行比較。可以看的出來,當子節點 > 父節點時會停止(這裡的 > 看比較器是怎麼比的),說明堆預設是小根堆???
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
// 這段程式碼完全可以直接呼叫下面的方法啊。。。
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;
}
// 使用給定的比較器,從堆底部開始向上進行比較。
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;
}
// 使用預設的比較器,從下表為 k 的節點作為父節點開始向下比較
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
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = array[child];
int right = child + 1;
// 找到下標為 k 的節點的左右子節點,並將其中的最小值與堆底的節點進行比較
if (right < n &&
((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
// 如果 x 的值小於 k 的最小子節點,那麼就跳出迴圈,把 x 賦值給下標 k 的位置(一般在整個堆有序的情況下,只有在進行葉子節點的父節點的位置才會發生。因為堆是有序的,預設 x 是大於上面的節點的)。否則將子節點中的更小值'上浮',並從子節點開始繼續向下比較。(常威,還說你不是小根堆???!!!)
if (key.compareTo((T) c) <= 0)
break;
array[k] = c;
k = child;
}
array[k] = key;
}
}
// 使用預設的比較器,從下表為 k 的節點作為父節點開始向下比較。邏輯跟上面的差不多,只是將比較方法使用比較器的 compare 方法替換
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;
}
}
// 整個堆都進行'下沉'操作,使整個堆變得有序
private void heapify() {
Object[] array = queue;
int n = size;
int half = (n >>> 1) - 1;
Comparator<? super E> cmp = comparator;
if (cmp == null) {
for (int i = half; i >= 0; i--)
siftDownComparable(i, (E) array[i], array, n);
}
else {
for (int i = half; i >= 0; i--)
siftDownUsingComparator(i, (E) array[i], array, n, cmp);
}
}
5-2-5、介面方法實現
下面是介面方法的實現:
// 增加元素,呼叫 offer 方法
public boolean add(E e) {
return offer(e);
}
// 增加元素的底層方法,其他的方法都是呼叫它
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
// 獲取鎖
lock.lock();
int n, cap;
Object[] array;
// 如果元素個數大小 >= 陣列大小(???這個不應該是嚴格相等的嘛???驚了!!!),執行擴容操作
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);
// 元素個數 +1
size = n + 1;
// 喚醒那些因為佇列空了而掛起的執行緒
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
// 增加元素,呼叫 offer 方法
public void put(E e) {
offer(e); // never need to block
}
// 增加元素,呼叫 offer 方法。可以看到這個給的時間限制根本沒用!!!因為這個阻塞佇列是真正無界的!永遠不會滿!因此增加元素時不會因為滿了而掛起,只要鎖被釋放了就會去增加
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e); // never need to block
}
// 出隊元素。執行出隊策略。如果佇列空了顯然就返回 null 了也不會報錯。。。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
// 出隊元素。執行出隊策略。如果佇列空了就掛起,除非被打斷
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;
}
// 出隊元素。執行出隊策略。如果佇列空了就掛起,直到超時或被打斷
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null && nanos > 0)
nanos = notEmpty.awaitNanos(nanos);
} finally {
lock.unlock();
}
return result;
}
// 直接獲取堆頂元素而不修改陣列
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (size == 0) ? null : (E) queue[0];
} finally {
lock.unlock();
}
}
// 返回比較器,如果沒有那就是初始值 null
public Comparator<? super E> comparator() {
return comparator;
}
// 返回佇列元素個數
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return size;
} finally {
lock.unlock();
}
}
// 返回剩餘容量。。。要知道雖然返回了 Integer.MAX_VALUE 但其實不止,只要記憶體夠,無限的好哇!
public int remainingCapacity() {
return Integer.MAX_VALUE;
}
// 獲取元素 o 的下標,使用得是 equals 方法而不是 ==,因此是指同一個元素物件。(沒有鎖,會出問題的吧!)
private int indexOf(Object o) {
if (o != null) {
Object[] array = queue;
int n = size;
for (int i = 0; i < n; i++)
if (o.equals(array[i]))
return i;
}
return -1;
}
// 刪除指定下標的元素
private void removeAt(int i) {
Object[] array = queue;
int n = size - 1;
// 如果是最後一個元素,那就直接刪了即可
if (n == i) // removed last element
array[i] = null;
// 否則要進行堆重排操作
else {
// 拿出最後一個元素來進行重排
E moved = (E) array[n];
array[n] = null;
Comparator<? super E> cmp = comparator;
// 從位置 i 下沉進行重排(為什麼???此時不應該是有序的嘛?)
if (cmp == null)
siftDownComparable(i, moved, array, n);
else
siftDownUsingComparator(i, moved, array, n, cmp);
// 重排後如果原來刪除位置的元素 == 堆底元素,從 i 上浮(???更看不懂了?為什麼?)
if (array[i] == moved) {
if (cmp == null)
siftUpComparable(i, moved, array);
else
siftUpUsingComparator(i, moved, array, cmp);
}
}
size = n;
}
// 刪除元素 o,注意這邊是 indexOf() 方法,用的 equals 方法,跟下面有點不同
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = indexOf(o);
if (i == -1)
return false;
removeAt(i);
return true;
} finally {
lock.unlock();
}
}
// 刪除元素 o,比較的是地址
void removeEQ(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] array = queue;
for (int i = 0, n = size; i < n; i++) {
if (o == array[i]) {
removeAt(i);
break;
}
}
} finally {
lock.unlock();
}
}
// 查詢是否有元素 o
public boolean contains(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return indexOf(o) != -1;
} finally {
lock.unlock();
}
}
// 複製了一個陣列
public Object[] toArray() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return Arrays.copyOf(queue, size);
} finally {
lock.unlock();
}
}
// toString 方法
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = size;
if (n == 0)
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < n; ++i) {
Object e = queue[i];
sb.append(e == this ? "(this Collection)" : e);
if (i != n - 1)
sb.append(',').append(' ');
}
return sb.append(']').toString();
} finally {
lock.unlock();
}
}
// 將元素搬到 c 中,呼叫了下面的方法
public int drainTo(Collection<? super E> c) {
return drainTo(c, Integer.MAX_VALUE);
}
// 將元素搬到 c 中,執行將重複出隊策略。。。(我靠,那每次出隊還得下沉。。。為啥不直接把陣列給複製過去算了???反正有序啊。。。看不懂)
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = Math.min(size, maxElements);
for (int i = 0; i < n; i++) {
c.add((E) queue[0]); // In this order, in case add() throws.
dequeue();
}
return n;
} finally {
lock.unlock();
}
}
// 清楚所有元素,可以看到陣列長度沒有重置,還是原來的大小
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] array = queue;
int n = size;
size = 0;
for (int i = 0; i < n; i++)
array[i] = null;
} finally {
lock.unlock();
}
}
// 把元素放到 a 中。如果 a 不夠大,那麼直接返回阻塞佇列儲存陣列的 copy
public <T> T[] toArray(T[] a) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int n = size;
if (a.length < n)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(queue, size, a.getClass());
System.arraycopy(queue, 0, a, 0, n);
if (a.length > n)
a[n] = null;
return a;
} finally {
lock.unlock();
}
}
5-2-6、其他內部類和方法
後面是序列化反序列化方法以及迭代器內部類和拆分器內部類。由於這些內容和阻塞佇列核心不怎麼相關,就擱置不講了,以後有機會再補。
5-2-7、unsafe 機制擴充
// 補充了靜態程式碼塊來幫助擴容的欄位
private static final sun.misc.Unsafe UNSAFE;
private static final long allocationSpinLockOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = PriorityBlockingQueue.class;
// 通過反射將 allocationSpinLock 欄位賦值給 allocationSpinLockOffset(???)
allocationSpinLockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("allocationSpinLock"));
} catch (Exception e) {
throw new Error(e);
}
}
5-1、PriorityBlockingQueue 概述
看到這個阻塞佇列,說實話三觀有點崩壞。有時候程式設計師出問題,並不一定是程式設計師水平不到位,也有可能是原始碼出問題了喂!也有可能是網上的部落格說的根本就是照搬人家的東西啊!也有可能官方說明就不對啊!!!下面來對這個阻塞佇列進行總結:
PriorityBlockingQueue
是一個無界(真正無界!)的阻塞佇列,自帶擴容機制,因此容易 OOM ?;PriorityBlockingQueue
內部使用陣列儲存元素實現小根堆(當然了,我們自己使用比較器,強行把比較結果調換也能實現大根堆),因此優先值越小的代表優先順序越高,先執行。。。跟我們普通人理解的不一樣啊喂orz;PriorityBlockingQueue
是使用ReentrantLock
的非公平策略,但是由於堆的構造,所以入隊公不公平他不care;- 內部使用 Condition 機制是程式進行休眠和喚醒;
- 整個內部對於高併發的處理不夠嚴謹,包括建構函式中沒有用鎖,可能會出現執行緒A構造玩陣列還在將集合 c 中的元素搬過來的時候,執行緒B已經開始使用了,這樣元素的個數也不對了;
宣告
由於篇幅太長,準備將這篇分為上中下三部分,敬請期待。。。