1. 程式人生 > >並發隊列阻塞式與非阻塞式的區別

並發隊列阻塞式與非阻塞式的區別

否則 rabl 添加元素 生產者 peek add 獲取 block 雙端隊列

在並發隊列上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 (並發同步阻塞隊列)

ConcurrentLinkedQueue

ConcurrentLinkedQueue : 是一個適用於高並發場景下的隊列,通過無鎖的方式,實現

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

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

public static void main(String[] args) {
//非阻塞式隊列 無界隊列(不能設置隊列大小,可以存儲很多)
ConcurrentLinkedQueue clq=new ConcurrentLinkedQueue<String>();
clq.offer("zhangsan");
clq.offer("lisi");
clq.offer("wangwu");
//獲取單個隊列信息 poll 獲取隊列之後 會刪除隊列信息 移除隊列信息
System.out.println("獲取隊列總數:"+clq.size());
System.out.println("單個隊列信息:"+clq.poll());
System.out.println("獲取隊列總數:"+clq.size());

//peek 不會刪除隊列信息 這個方法在實際的應用中 用的較少 因為隊列被消費之後 應該被刪除 否則會產生問題
System.out.println(clq.peek());
//獲取隊列總數
System.out.println("獲取隊列總數:"+clq.size());
}

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隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。

並發隊列阻塞式與非阻塞式的區別