上一篇咱們已經學習過了 ArrayBlockingQueue的知識及相關方法的使用,這一篇咱們就來再學習一下ArrayBlockingQueue的親戚 LinkedBlockingQueue。在集合類中 ArrayList與 LinkedList會經常拿來比較,ArrayList內部實現是基於數組的,而 LinkedList內部實現是基於鏈表,因此他們之間會有不少不一樣,可是本文不會去重點討論,感興趣的朋友能夠參考我以前發過的幾篇文章,那麼有請本節的主角 LinkedBlockingQueue!node
LinkedBlockingQueue數組
LinkedBlockingQueue是一個一個基於已連接節點的、範圍任意(相對而論)的 blocking queue。此隊列按 FIFO(先進先出)排序元素。隊列的頭部 是在隊列中時間最長的元素。隊列的尾部 是在隊列中時間最短的元素。新元素插入到隊列的尾部,而且隊列獲取操做會得到位於隊列頭部的元素。連接隊列的吞吐量一般要高於基於數組的隊列,可是在大多數併發應用程序中,其可預知的性能要低。 併發
可選的容量範圍構造方法參數做爲防止隊列過分擴展的一種方法。若是未指定容量,則它等於 Integer.MAX_VALUE。除非插入節點會使隊列超出容量,不然每次插入後會動態地建立連接節點。 性能
LinkedBlockingQueue及其迭代器實現了 Collection 和 Iterator 接口的全部可選 方法。 學習
咱們已經學習過了 ArrayBlockingQueue,因此學習 LinkedBlockingQueue就天然比較輕鬆,因此本文對於已經明確的相關概念就不作過多介紹了,而是重點放在二者的區別之上。this
1.成員變量spa
與ArrayBlockingQueue不一樣 LinkedBlockingQueue的成員變量有些變化,如下是 LinkedBlockingQueue的成員變量:線程
1)首先 LinkedBlockingQueue明確了容量變量,當爲指定容量時,默認容量爲Int的最大值Integer.MAX_VALUE。對象
2)隊列元素數量變量 count採用的是 AtomicInteger ,而不是普通的Int型。CAS相關可參考http://286.iteye.com/blog/2295165blog
3)LinkedBlockingQueue內部隊列實現使用的是 Node節點類,這與 LinkedList相似。
4)最後也是最重要的一點,那就是獲取與插入操做分紅了兩個鎖:takeLock與 putLock來處理,這點下面還會重點分析。
2.構造方法
有三個構造方法,分別爲默認,指定容量,指定容量和初始元素。
默認構造方法建立一個容量爲 Integer.MAX_VALUE的 LinkedBlockingQueue實例。
第二種構造方法,指定了隊列容量,首先判斷指定容量是否大於零,不然拋出異常。而後爲 capacity 賦值,最後建立空節點,並指向 head與 last,二者的 item與 next此時均爲 null。
最後一種,利用循環向隊列中添加指定集合中的元素。
3.Node類
LinkedBlockingQueue內部列表實現是使用的 Node內部類,Node類也並不複雜,如下是其源代碼:
item用於表示元素對象,next指向鏈表的下一個節點。
LinkedBlockingQueue的大部分方法實際上是與 ArrayBlockingQueue相似的,因此本文就只介紹不一樣於ArrayBlockingQueue的相關方法。
4.添加元素
1)add方法
add方法相同就不介紹了,一樣調用的是offer方法。
2)offer方法
將指定元素插入到此隊列的尾部(若是當即可行且不會超出此隊列的容量),在成功時返回 true,若是此隊列已滿,則返回 false。當使用有容量限制的隊列時,此方法一般要優於 add 方法,後者可能沒法插入元素,而只是拋出一個異常。
與ArrayBlockingQueue不一樣,LinkedBlockingQueue多了一些容量方面的判斷。
能夠看到offer方法的關鍵在於 insert方法。
3)insert方法
insert方法很是簡單,可是卻不要小看。
首先,根據指定參數x建立一個Node實例。
而後,將原尾節點的next指向此節點。
最後,將尾節點設置尾此節點。
這樣新添加的節點就成爲了新的尾節點。
當向鏈表中添加第一個節點時,由於在初始化時
因此此時 head與 last指向的是同一個對象new Node<E>(null)。
以後將last.next指向x。
由於此時 head與 last是同一個對象,因此 head.next也指向x。
最後將 last指向x。
這樣 head的next就指向了 last。此時head中的 item仍爲 null。
4)put方法
將指定元素插入到此隊列的尾部,若有必要,則等待空間變得可用。
5.獲取元素
1)peek方法
peek方法獲取但不移除此隊列的頭;若是此隊列爲空,則返回 null。
peek方法從頭節點直接就能夠獲取到第一個添加的元素,因此效率是比較高的。若是不存在則返回null。
2)poll方法
poll方法獲取並移除此隊列的頭,若是此隊列爲空,則返回 null。
poll與 peek方法不一樣在於poll獲取完元素後移除這個元素,獲取與移除是經過 extract()方法實現的。
注意:其中須要注意的是最後部分代碼:
確定會有朋友有如下疑問:
1)隊列都已經滿了,還須要喚醒添加線程幹什麼?
2)線程滿了就不該該再向裏面添加元素了啊?
3)signalNotFull方法是幹什麼的?
signalNotFull方法的做用是喚醒等待中的put線程,signalNotFull只能被 take/poll方法調用,如下是 signalNotFull方法的源代碼:
前兩點問題其實轉換一下角度就能很好的理解了,雖然隊列已經滿了,可是此時本線程已經完成了添加,可是其餘線程還在等待獲取條件進行添加,若是不去主動喚醒的話,那麼這些添加操做就只能無限期的等待下去,因此這些等待的添加操做就會失效。因此此時須要喚醒已經排隊的添加線程,雖然他們已經沒法添加元素至隊列。
3)extract方法
extract方法用於獲取並移除頭節點。
這裏須要注意的是這裏指的頭節點並非 head,而是 head的 next所指 Node的 item元素。由於 head的 item永遠爲 null。last的 next永遠爲 null。
4)take方法
獲取並移除此隊列的頭部,在元素變得可用以前一直等待(若是有必要)。
與 poll方法相似,只是take方法採用阻塞的方式來獲取元素。
7.其餘方法
1)remainingCapacity方法
也就是返回能夠當即添加元素的數量。
2)iterator方法
iterator方法返回在隊列中的元素上按適當順序進行迭代的迭代器。返回的 Iterator 是一個「弱一致」的迭代器,從不拋出 ConcurrentModificationException,而且確保可遍歷迭代器構造後所存在的全部元素,而且可能(但並不保證)反映構造後的全部修改。
iterator方法返回的是一個Itr內部類的實例,經過這個實例能夠遍歷整個隊列。如下是Itr內部類的源代碼:
Itr類不復雜,我就不詳細解釋了。
3)清除方法
clear,drainTo等方法與 ArrayBlockingQueue相似,這裏就不說了。
8,.LinkedBlockingQueue與 ArrayBlockingQueue
1)內部實現不一樣
ArrayBlockingQueue內部隊列存儲使用的是數組:
而 LinkedBlockingQueue內部隊列存儲使用的是Node節點內部類:
2)隊列中鎖的實現不一樣
從源代碼就能夠看出 ArrayBlockingQueue實現的隊列中的鎖是沒有分離的,即添加與獲取使用的是同一個鎖;而 LinkedBlockingQueue實現的隊列中的鎖是分離的,即添加用的是 putLock,獲取是 takeLock。
3)初始化條件不一樣
ArrayBlockingQueue實現的隊列中必須指定隊列的大小。
LinkedBlockingQueue實現的隊列中能夠不指定隊列的大小,默認容量爲Integer.MAX_VALUE。
4)操做不一樣
ArrayBlockingQueue不管是添加仍是獲取使用的是同一個鎖,因此添加的同時就不能讀取,讀取的同時就不能添加,因此鎖方面性能不如 LinkedBlockingQueue。
LinkedBlockingQueue讀取與添加操做使用不一樣的鎖,由於其內部實現的特殊性,添加的時候只須要修改 last便可,而不會影響 head節點。而獲取時也只須要修改 head節點便可,一樣不會影響 last節點。因此在添加獲取方面理論上性能會高於 ArrayBlockingQueue。
因此 LinkedBlockingQueue更適合實現生產者-消費者隊列。