並發阻塞隊列和非阻塞隊列詳解


在並發隊列上JDK提供了兩套實現,一個是以ConcurrentLinkedQueue為代表的高性能隊列非阻塞,一個是以BlockingQueue接口為代表的阻塞隊列,無論哪種都繼承自Queue。

隊列遵循先進先出,后進后出的原則

阻塞式隊列與非阻塞隊列的區別:

阻塞式隊列:

  入列(存):阻塞式隊列,如果存放的隊列超出隊列的總數,是時候會進行等待(阻塞)。當隊列達到總數的時候,入列(生產者)會進行阻塞。這時候只有當消費者消費了隊列中的隊列之后,生產者才可以繼續往隊列中存放隊列。

  出列(取):如果獲取隊列為空的情況下,這時候也會進行等待(阻塞)。這時候隊列中沒有隊列,消費者無法消費隊列,只有生產者往對隊列中存放隊列之后,消費者才可以進行消費。

隊列中的隊列如果被消費了就會從隊列中刪除掉。

白話文描述:

阻塞隊列與普通隊列的區別在於,當隊列是空的時,從隊列中獲取元素的操作將會被阻塞,或者當隊列是滿時,往隊列里添加元素的操作會被阻塞。試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊列插入新的元素。同樣,試圖往已滿的阻塞隊列中添加新元素的線程同樣也會被阻塞,直到其他的線程使隊列重新變得空閑起來,如從隊列中移除一個或者多個元素,或者完全清空隊列.

1.ArrayDeque, (數組雙端隊列) 
2.PriorityQueue, (優先級隊列) 
3.ConcurrentLinkedQueue, (基於鏈表的並發隊列) 
4.DelayQueue, (延期阻塞隊列)(阻塞隊列實現了BlockingQueue接口) 
5.ArrayBlockingQueue, (基於數組的並發阻塞隊列) 
6.LinkedBlockingQueue, (基於鏈表的FIFO阻塞隊列) 
7.LinkedBlockingDeque, (基於鏈表的FIFO雙端阻塞隊列) 
8.PriorityBlockingQueue, (帶優先級的無界阻塞隊列) 
9.SynchronousQueue (並發同步阻塞隊列)

下表是阻塞隊列的部分方法:

方法\處理方式 拋出異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用

ConcurrentLinkedQueue(非阻塞隊列)

ConcurrentLinkedQueue : 是一個適用於高並發場景下的隊列,通過無鎖的方式,實現了高並發狀態下的高性能,通常ConcurrentLinkedQueue性能好於BlockingQueue.它是一個基於鏈接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。頭是最先加入的,尾是最近加入的,該隊列不允許null元素。

ConcurrentLinkedQueue重要方法:
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,后者不會。

BlockingQueue

阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:

在隊列為空時,獲取元素的線程會等待隊列變為非空。當隊列滿時,存儲元素的線程會等待隊列可用。

阻塞隊列常用於生產者和消費者的場景,生產者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器里拿元素。

BlockingQueue即阻塞隊列,從阻塞這個詞可以看出,在某些情況下對阻塞隊列的訪問可能會造成阻塞。被阻塞的情況主要有如下兩種:

1. 當隊列滿了的時候進行入隊列操作

2. 當隊列空了的時候進行出隊列操作

因此,當一個線程試圖對一個已經滿了的隊列進行入隊列操作時,它將會被阻塞,除非有另一個線程做了出隊列操作;同樣,當一個線程試圖對一個空隊列進行出隊列操作時,它將會被阻塞,除非有另一個線程進行了入隊列操作。

在Java中,BlockingQueue的接口位於java.util.concurrent 包中(在Java5版本開始提供),由上面介紹的阻塞隊列的特性可知,阻塞隊列是線程安全的。

在新增的Concurrent包中,BlockingQueue很好的解決了多線程中,如何高效安全“傳輸”數據的問題。通過這些高效並且線程安全的隊列類,為我們快速搭建高質量的多線程程序帶來極大的便利。本文詳細介紹了BlockingQueue家庭中的所有成員,包括他們各自的功能以及常見使用場景。

認識BlockingQueue

阻塞隊列,顧名思義,首先它是一個隊列,而一個隊列在數據結構中所起的作用大致如下圖所示:

從上圖我們可以很清楚看到,通過一個共享的隊列,可以使得數據由隊列的一端輸入,從另外一端輸出;

常用的隊列主要有以下兩種:(當然通過不同的實現方式,還可以延伸出很多不同類型的隊列,DelayQueue就是其中的一種)

  先進先出(FIFO):先插入的隊列的元素也最先出隊列,類似於排隊的功能。從某種程度上來說這種隊列也體現了一種公平性。

  后進先出(LIFO):后插入隊列的元素最先出隊列,這種隊列優先處理最近發生的事件。

      多線程環境中,通過隊列可以很容易實現數據共享,比如經典的“生產者”和“消費者”模型中,通過隊列可以很便利地實現兩者之間的數據共享。假設我們有若干生產者線程,另外又有若干個消費者線程。如果生產者線程需要把准備好的數據共享給消費者線程,利用隊列的方式來傳遞數據,就可以很方便地解決他們之間的數據共享問題。但如果生產者和消費者在某個時間段內,萬一發生數據處理速度不匹配的情況呢?理想情況下,如果生產者產出數據的速度大於消費者消費的速度,並且當生產出來的數據累積到一定程度的時候,那么生產者必須暫停等待一下(阻塞生產者線程),以便等待消費者線程把累積的數據處理完畢,反之亦然。然而,在concurrent包發布以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復雜度。好在此時,強大的concurrent包橫空出世了,而他也給我們帶來了強大的BlockingQueue。(在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒)

ArrayBlockingQueue

ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。

ArrayBlockingQueue是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。

LinkedBlockingQueue

LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是采用了默認大小為Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。

PriorityBlockingQueue

PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣。需要注 意,PriorityBlockingQueue中允許插入null對象。

所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就 是按照我們對這個接口的實現來定義的。另外,我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器並不保證按照優先級順 序進行迭代。

SynchronousQueue

SynchronousQueue隊列內部僅允許容納一個元素。當一個線程插入一個元素后會被阻塞,除非這個元素被另一個線程消費。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM