Java BlockingQueue 源碼分析,javablockingqueue
簡介
BlockingQueue 是 Java concurrent包提供的多安全執行緒的阻塞隊列,其子類包括 LinkedBlockingQueue 和 ArrayBlockingQueue。
關鍵API
說到隊列,自然少不了首尾的插入刪除操作,BlockingQueue的API中提供了好幾種插入刪除方法。
這些方法在遇到無法滿足的執行條件時,如隊列滿了(添加元素時)/隊列為空白(取出元素時),會採取不同的措施:拋出異常,返回false/null,阻塞調用API的線程,等待一定時間等。具體如下表:
|
Throws exception |
Special value |
Blocks |
Times out |
| Insert |
add(e) |
offer(e) |
put(e) |
offer(e,time,unit) |
| Remove |
remove() |
poll() |
take() |
poll(time,unit) |
| Examine |
element() |
peek() |
not applicable |
not applicable |
ArrayBlockingQueue
ArrayBlockingQueue是一個基於數組的阻塞隊列,在建立一個ArrayBlockingQueue時,需要提供的一個表示隊列大小的參數。
ArrayBlockingQueue是安全執行緒的,但這是怎麼做到的呢?這需要注意類中的三個屬性:
/** Main lock guarding all access */ final ReentrantLock lock; /** Condition for waiting takes */ private final Condition notEmpty; /** Condition for waiting puts */ private final Condition notFull;
這裡一個鎖,兩個條件變數,管理所有使用API的線程的互斥和同步。具體的鎖和條件變數的理論知識,可以參見相關的作業系統書籍。
offer vs put
public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { insert(e); return true; } } finally { lock.unlock(); } } public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); insert(e); } finally { lock.unlock(); } }
可以看到,兩個方法中在往隊列裡添加元素時,都是先對臨界區加鎖,不同在於,offer方法中若是檢測出隊列已滿,會直接返回false;put方法中,若是檢測出隊列已滿,線程會在 notFull 條件變數中阻塞,這樣線程會釋放鎖,讓其他線程進入臨界區。以後某個時間,一個線程從隊列中取出元素,隊列不再為空白,並且該線程喚醒在notFull條件變數上阻塞的線程,put方法才有可能完成。
poll vs take
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return extract(); } finally { lock.unlock(); } }
take的邏輯和put差不多,只不過這次是若隊列為空白,則線程阻塞在notEmpty 條件變數上,等待其他線程往隊列中添加元素,並喚醒在notEmpty上阻塞的線程。
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : extract(); } finally { lock.unlock(); } }
poll方法在檢測到隊列為空白時,直接返回null,否則取出隊頭元素。
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 extract(); } finally { lock.unlock(); } }
poll 還有個比較有趣的重載實現,這裡利用Condition變數的計時器方法awaitNanos 。先將時間大小根據時間單位換算成納秒的數值,當隊列容量為0是,使用Condition.awaitNanos(…),進行計時,逾時後返回null。