爲啥用阻塞隊列,list 不行嗎?
1、什麼是阻塞隊列?
阻塞隊列是一種隊列,阻塞隊列是一種特殊的隊列。
阻塞隊列是一種可以在多線程環境下使用,並且支持阻塞等待的隊列。
線程 1 往阻塞隊列中添加元素,當阻塞隊列是滿的,線程 1 就會阻塞,直到隊列不滿
線程 2 從阻塞隊列中移除元素,當阻塞隊列是空的,線程 2 會阻塞,直到隊列不空;
2、主要併發隊列關係圖
上圖展示了 Queue 最主要的實現類,可以看出 Java 提供的線程安全的隊列(也稱爲併發隊列)分爲阻塞隊列和非阻塞隊列兩大類。
BlockingQueue 下面有 6 種最主要的阻塞隊列實現,分別是
-
ArrayBlockingQueue
-
LinkedBlockingQueue
-
SynchronousQueue
-
DelayQueue
-
PriorityBlockingQueue
-
LinkedTransferQueue
非阻塞併發隊列的典型例子是 ConcurrentLinkedQueue,這個類不會讓線程阻塞,利用 CAS 保證了線程安全。
我們可以根據需要自由選取阻塞隊列或者非阻塞隊列來滿足業務需求。
還有一個和 Queue 關係緊密的 Deque 接口,它繼承了 Queue,如代碼所示:
public interface Deque<E> extends Queue<E> {//...}
Deque 的意思是雙端隊列,音標是 [dek],是 double-ended-queue 的縮寫,它從頭和尾都能添加和刪除元素;而普通的 Queue 只能從一端進入,另一端出去。這是 Deque 和 Queue 的不同之處,Deque 其他方面的性質都和 Queue 類似。
3、阻塞隊列和 List、Set 的區別是什麼?
阻塞隊列和 List、Set 一樣都繼承自 Collection。
阻塞隊列它和 List 的區別在於,List 可以在任意位置添加和刪除元素。
而阻塞隊列屬於 Queue 隊列的一種,Queue 只有兩個操作:
-
把元素添加到隊列末尾;
-
從隊列頭部取出元素。
超市的收銀臺就是一個隊列:
我們常用的 LinkedList 就可以當隊列使用,實現了 Dequeue 接口,還有 ConcurrentLinkedQueue,他們都屬於非阻塞隊列。
4、阻塞隊列和普通 Queue 隊列的區別是什麼?
阻塞隊列和一般的隊列的區別就在於:
-
多線程環境支持,多個線程可以安全的訪問隊列
-
支持生產和消費等待,多個線程之間互相配合,在某些情況下會掛起線程,一旦條件滿足,被掛起的線程又會自動被喚醒
-
當阻塞隊列是空的,消費線程會阻塞,從隊列中獲取元素的操作將會被阻塞,直到隊列不空
-
當阻塞隊列是滿的,生產線程就會阻塞,往隊列裏添加元素的操作將會被阻塞,直到隊列不滿
5、阻塞隊列的作用
阻塞隊列,也就是 BlockingQueue,它是一個接口,如代碼所示:
public interface BlockingQueue<E> extends Queue<E>{...}
BlockingQueue 繼承了 Queue 接口,是隊列的一種。
Queue 和 BlockingQueue 都是在 Java 5 中加入的。
BlockingQueue 是線程安全的,在很多場景下都可以利用線程安全的隊列來優雅地解決業務自身的線程安全問題。
比如說,使用生產者 / 消費者模式的時候,生產者只需要往隊列裏添加元素,而消費者只需要從隊列裏取出它們就可以了,如圖所示:
在圖中,左側有三個生產者線程,它會把生產出來的結果放到中間的阻塞隊列中,而右側的三個消費者也會從阻塞隊列中取出它所需要的內容並進行處理。因爲阻塞隊列是線程安全的,所以生產者和消費者都可以是多線程的,不會發生線程安全問題。
既然隊列本身是線程安全的,隊列可以安全地從一個線程向另外一個線程傳遞數據,所以生產者 / 消費者直接使用線程安全的隊列就可以,而不需要自己去考慮更多的線程安全問題。這也就意味着,考慮鎖等線程安全問題的重任從 “你” 轉移到了 “隊列” 上,降低了開發的難度和工作量。
同時,隊列還能起到一個隔離的作用。
比如說開發一個銀行轉賬的程序,那麼生產者線程不需要關心具體的轉賬邏輯,只需要把轉賬任務,如賬戶和金額等信息放到隊列中就可以,而不需要去關心銀行這個類如何實現具體的轉賬業務。而作爲銀行這個類來講,它會去從隊列裏取出來將要執行的具體的任務,再去通過自己的各種方法來完成本次轉賬。
這樣就實現了具體任務與執行任務類之間的解耦,任務被放在了阻塞隊列中,而負責放任務的線程是無法直接訪問到銀行具體實現轉賬操作的對象的,實現了隔離,提高了安全性。
6、阻塞隊列的功能
阻塞隊列區別於其他類型的隊列的最主要的特點就是 “阻塞” 這兩個字,
所以下面重點介紹阻塞功能:阻塞功能使得生產者和消費者兩端的能力得以平衡,當有任何一端速度過快時,阻塞隊列便會把過快的速度給降下來。
7、阻塞隊列的核心方法
-
拋異常的方法 就是在插入滿了之後,會報一個異常,remove 一樣,element 是檢查隊頭的元素或者是否爲空。
-
特殊值的方法是在插入滿之後返回值變成了 false 而不是一個異常,取出失敗的時候返回 null。
-
阻塞方法是在插入滿之後把這個方法阻塞,一直等待隊列空出來一個之後再進行加入,會出現一直等待,也可能出現飢餓現象。
-
超時方法的話,當阻塞隊列滿時,隊列會阻塞生產者線程一定時間,超過限時後生產者線程會退出。
實現阻塞最重要的兩個方法是 take 方法和 put 方法。
7.1 take 方法
take 方法的功能是獲取並移除隊列的頭結點,通常在隊列裏有數據的時候是可以正常移除的。
可是一旦執行 take 方法的時候,隊列裏無數據,則阻塞,直到隊列裏有數據。
一旦隊列裏有數據了,就會立刻解除阻塞狀態,並且取到數據。
過程如圖所示:
7.2 put 方法
put 方法插入元素時,如果隊列沒有滿,那就和普通的插入一樣是正常的插入,但是如果隊列已滿,那麼就無法繼續插入,則阻塞,直到隊列裏有了空閒空間。
如果後續隊列有了空閒空間,比如消費者消費了一個元素,那麼此時隊列就會解除阻塞狀態,並把需要添加的數據添加到隊列中。
put 過程如圖所示:
以上過程中的阻塞和解除阻塞,都是 BlockingQueue 完成的,不需要我們自己處理。
7.3 是否有界(容量有多大)
此外,阻塞隊列還有一個非常重要的屬性,那就是容量的大小,分爲有界和無界兩種。
-
有的阻塞隊列是無界的,無界隊列意味着裏面可以容納非常多的元素,例如 LinkedBlockingQueue 的上限是 Integer.MAX_VALUE,約爲 2 的 31 次方,是非常大的一個數,可以近似認爲是無限容量,因爲幾乎無法把這個容量裝滿。
-
但是有的阻塞隊列是有界的,例如 ArrayBlockingQueue 如果容量滿了,也不會擴容,所以一旦滿了就無法再往裏放數據了。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/w4m6ZwMcWacWC9bMJ2a55A