阻塞隊列分析

轉載請標註來源:https://www.cnblogs.com/xmzJava/p/9380649.htmlhtml

前言


 在分析多線程的文章中,咱們知道了Executors是經過阻塞隊列接受任務。例如 FixedThreadPool 使用的是 LinkedBlockingQueue, CachedThreadPool 使用的是 SynchronousQueue。阻塞隊列的基類是 BlockingQueue,他的實現類以下所示api

 

BlockingQueue的api咱們須要重點關注下,理解了這些api的做用,對於實現類的分析會輕鬆不少。數組

類型 api名稱 是否阻塞 簡述
放入數據 offer(anObject) 將anObject加到BlockingQueue裏,即若是BlockingQueue能夠容納,則返回true,不然返回false
offer(E o, long timeout, TimeUnit unit) 能夠設定等待的時間,若是在指定的時間內,還不能往隊列中加入BlockingQueue,則返回失敗
put(anObject) 把anObject加到BlockingQueue裏,若是BlockQueue沒有空間,則調用此方法的線程被阻斷直到BlockingQueue裏面有空間再繼續
獲取數據 poll(time) 取走BlockingQueue裏排在首位的對象,若不能當即取出,則能夠等time參數規定的時間,取不到時返回null
poll(long timeout, TimeUnit unit) 同上
take() 取走BlockingQueue裏排在首位的對象,若BlockingQueue爲空,阻斷進入等待狀態直到BlockingQueue有新的數據被加入
drainTo() 一次性從BlockingQueue獲取全部可用的數據對象(還能夠指定獲取數據的個數),經過該方法,能夠提高獲取數據效率;不須要屢次分批加鎖或釋放鎖

 

 

 

 

 

 

 

 

重點關注表中的阻塞類型的方法,他們是阻塞隊列的核心。接下來說述幾個經常使用的阻塞隊列的存取數據api。緩存

ArrayBolckingQueue


在ArrayBlockingQueue內部,維護了一個定長數組,以便緩存隊列中的數據對象。咱們先看offer方法數據結構

咱們再看下 enqueue多線程

 

以上是非阻塞的存放。咱們再看阻塞版本的存放spa

能夠看到,兩種方式的差異其實很小,一個是阻塞一段時間後直接返回false,一個是無限期的阻塞。咱們再看下取數據的api。.net

再看下dequeue線程

 以上就是ArrayBlockingQunue的具體分析。debug

 

LinkedBlockingQueue


 LinkedBlockingQueue是基於鏈表的阻塞隊列,首先須要注意的是LinkedBlockingQueue是能夠無界的,當你不指定容量時他默認的大小是 Integer.MAX_VALUE

鏈表經過內部的Node來實現,能夠看出這是個單項鍊表。

接下來,咱們再看下具體的api 

咱們看下 enqueue,就是一個簡單的鏈表操做。

put的操做和offer類似,這裏就省略了,再看下poll

看下dequeue,把頭結點取出來,把下個節點設爲頭結點

以上就是LinkedBlockingQueue。

經過上述兩種隊列的講解,咱們大概知道了隊列存取元素的大體過程,其餘隊列和上述兩種隊列的api大體相同,因此接下重點講述隊列的大體特色,再也不對api進行詳細的描述。

 

PriorityQueue


     PriorityQueue並非阻塞隊列,在這裏講述是由於,接下來的幾個隊列都是基於他的擴展。PriorityQueue和ArrayBlockingQueue同樣,內部維護了一個定長數組,若是不指定長度,默認長度就是11。PriorityQueue是一個優先級隊列,元素的順序並非按照插入順序而來,而是按照元素的大小來判斷。默認的比較器是從小到大,即隊首的元素老是最小的。固然。能夠自定義比較器。

    PriorityQueue的優先級經過二叉小頂堆實現,他的邏輯結構是一棵徹底二叉樹,存儲結構實際上是一個數組。邏輯結構層次遍歷的結果恰好是一個數組。這裏借鑑網上的一幅圖

 

咱們看下添加元素的過程,這裏重點是siftUp方法

            

由上圖能夠看出,PriorityQunue 最關鍵的即是 比較-交換 步驟。 添加元素都是先放到最後,而後再與本身的父節點比較

再看下獲取元素的步驟

      

 這裏還有種狀況,就是方法,刪除中間的某個元素,這就是上述兩種變化的結合,首先刪除 i 下標的元素,而後把末尾的元素放置到 i 座標,先向下比較看看,再向上比較。具體的代碼這裏就省略了。

 以上就是PriorityQueue的具體分析。

 

DelayQueue


DelayQueue 是延遲阻塞隊列,隊列中的元素只有當其指定的延遲時間到了,纔可以從隊列中獲取到該元素。DelayQueue是一個沒有大小限制的隊列,所以往隊列中插入數據的操做(生產者)永遠不會被阻塞,而只有獲取數據的操做(消費者)纔會被阻塞。

DelayQueue內部經過PriorityQueue實現延遲的權重排序 ,其比較器是 Comparable<Delayed> 

主要看存元素的api

這裏關鍵的是  

first.getDelay(NANOSECONDS)>0

 因此咱們在實現 Delayed的時候 實現類裏面必定得要有時間能夠記錄到什麼過時,若是過時了必定要返回負數

DelayQueue平時比較少見,可是咱們能夠用它作一些很靈活的事情,例如緩存過時,空閒鏈接去除。按照DelayQueue的特性,隊首的元素老是最早過時的,咱們能夠用一個後臺線程監聽DelayQueue的隊首元素。

這裏  你們能夠參考下用 DelayQueue實現一個過時緩存清除的功能。

 

PriorityBlockingQueue


 PriorityBlockingQueue是優先級阻塞隊列,他和咱們上文講的PriorityQueue很是類似。可是呢他又多了點阻塞的東西,準確來講是多了半點。由於在作put操做的時候是不會有阻塞的

就算咱們給他一個初始大小,可是若是容量不夠還會去擴容

因此咱們在往裏面put的時候要注意,萬一輩子產者一直在生產,消費者掛了,那麼內存很容易就會被耗盡

 

SynchronousQueue


 在線程池裏面,你們必定見到過這個隊列。這是一個不直接存放元素的隊列,他存儲的其實是他內置的Node。它生產產品(即put的時候),若是當前沒有人想要消費產品(即當前沒有線程執行take),今生產線程必須阻塞,等待一個消費線程調用take操做,take操做將會喚醒該生產線程,同時消費線程會獲取生產線程的產品。具體的流程我舉個例子

步驟一,調用put線程,存儲元素

 

步驟二,再次調用put線程,存儲元素 ,在調用一個take線程,取出一個元素

這個時候take線程會和put("b")配對,配對成功後put("a")線程就會成爲頭結點,整個流程以下圖所示

SynchronousQueue有兩種模式,公平和非公平,默認是非公平的。內部有兩種數據結構,分別是是隊列和棧來表示公平和非公平兩種模式。上圖所描述的是非公平模式,即先進後出。具體細節仍是得從代碼裏看。

首先看下存取元素的api

 

 

兩個方法都調用的  transfer 方法,這就是一個配對的方法。這裏截取核心部分的transfer方法供你們參考

這裏採用了大量的CAS操做進行更新,初看有點亂,可是心中熟記這是個配對方法,再debug幾回。代碼就會很清晰了。

 


 參考:

https://blog.csdn.net/u013309870/article/details/71189189 【Java堆結構PriorityQueue徹底解析】

http://www.cnblogs.com/leesf456/p/5560362.html 【SynchronousQueue分析】

相關文章
相關標籤/搜索