Queue概覽
如圖所示,在並發隊列上,JDK提供了2套實現,一個是以ConcurrentLinkedQueue為代表的高性能非阻塞隊列,一個是以BlockingQueue接口為代表的阻塞隊列,無論哪種都繼承自Queue。使用阻塞演算法的佇列可以用一個鎖(入隊和出隊用同一把鎖)或兩個鎖(入隊和出隊用不同的鎖)等方式來實現,而非阻塞的實現方式則可以使用循環CAS的方式來實現,下面我們來一一分析。
ConcurrentLinkedQueue
一個適用於高並發場景下的隊列,透過無鎖定的方式(CAS+volatile),實現了高並發下的高效能,通常Concurrented
它是一個基於連結節點的無界線程安全隊列,遵循先進先出的原則,頭是最先加入的,尾是最近加入的,不允許加入null元素。
注意add()/offer()都是加入元素的方法,這裡沒有差別;poll()/peek()是取出頭元素的方法,區別點在於poll會刪除元素,而peek不會。
要特別注意到由於它的非阻塞性,並不像其他普通集合那樣,獲取隊列的SIZE的方法並不是常數時間的花費,而是O(N)的,因此我們應該盡可能避免使用size ()方法,可以考慮使用isEmpty()來代替。
雖然使用到了CAS+VOLATILE的機制避免了鎖,但是我們要明白的是,這只是保證單一操作,如peek()的安全,但是多個操作如果想保證的話,需要使用鎖機制來達到同步的效果。
BlockingQueue API
入隊:
offer(E e):如果隊列沒滿,立即返回true; (E e):如果隊列滿了,一直阻塞,直到數組不滿了或者線程被中斷-->阻塞
offer(E e, long timeout, TimeUnit unit):在隊尾插入一個元素,,如果數組已滿,則進入等待,直到等待時間超時
出隊:
poll():非阻塞拿數據,立即返回
take():阻塞拿數據
p ):帶有一定超時時間的poll拿取資料
ArrayBlockingQueue
基於數組的阻塞隊列實現,在其內部維護了一個定長數組ArrayBlockingQueue內部只有一個鎖物件(ReentrantLock),因此讀寫沒有實現分離,也就意味著生產消費不能完全並行。由於長度需要定義,因此也叫有界隊列。
LinkedBlockingQueue
基於鍊錶的阻塞隊列實現,與ArrayBlockingQueue類似,其內部也維持一個資料緩衝隊列(鍊錶構成)。
LinkedBlockingQueue之所以較ArrayBlockingQueue更有效率的處理並發數據,是因為內部實現採用了2把鎖,也就是實現了入隊、出隊分別上鎖,即讀寫分離,從而生產者、消費者完全到達了並行。
無需定義長度,也叫無界隊列。當然不定義長度時,需要注意下生產者的速度和消費者的速度,因為預設情況下佇列長度是Integer.MAX_VALUE。
SynchronousQueue
一個沒有緩衝的隊列,生產者生產的資料會直接被消費者取得並消費。它是一個輕量級的阻塞佇列,因為不具備容量,在用法上,只能是一個執行緒阻塞著取元素,等待另一個執行緒往佇列裡面放入一個元素,然後會被等待的執行緒立即取走,其實就是實作了線程間的輕量級的單元素交換。
PriorityBlockingQueue
基於優先權的阻塞隊列(優先權的判斷透過建構子傳入的Compator物件決定,也就是傳入佇列中物件必須實現Comparable介面)。
在實作PriorityBlockingQueue時,內部控制執行緒同步的鎖採用的是公平鎖,也是一個無界的佇列。
通俗的來說,不是先進先出的隊列了,而是誰的優先級低誰先出去。那麼可以思考下,是否每次add/offer都會進行一次排序呢?我們是否需要按照優先順序進行全排序呢?實際上,可以大致看一看add/take方法,會了解到PriorityBlockingQueue的設計思想:在add時,並不進行排序處理,當進行take時,選擇優先級最小的拿出來而已,這樣既避免了在add時花時間排序,又在take時節省了時間,因為並沒有全排序,僅僅是挑選了一個優先級低的元素而已。
DelayQueue
帶有延遲時間的Queue,其中的元素只有當指定的延遲時間到了,才能從佇列中取得到該元素。佇列中的元素必須實作Delayed接口,沒有大小限制。本質上來說,是藉助PriorityBlockingQueue來實現的,以延遲時間作為優先權。延遲佇列的應用場景很多,例如快取逾時的資料進行移除,任務逾時處理,空閒連線的關閉等等。