Java併發編程實戰——併發隊列


Java 提供的線程安全的 Queue 可以分爲阻塞隊列和非阻塞隊列,其中阻塞隊列的典型例子是 BlockingQueue,非阻塞隊列的典型例子是 ConcurrentLinkedQueue,在實際應用中要根據實際需要選用阻塞隊列或者非阻塞隊列。 阻塞隊列可以通過加鎖來實現,非阻塞隊列可以通過 CAS 操作實現。

非阻塞隊列:ConcurrentLinkedQueue

首先ConcurrentLinkedQueue基本結構於LinkedQueue差不多,由head節點和tail節點組成,每個節點(Node)由節點元素(item)和指向下一個節點的引用(next)組成,節點與節點之間就是通過這個next關聯起來,從而組成一張鏈表結構的隊列。

ConcurrentLinkedQueue 內部代碼我們就不分析了,大家知道 ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法來實現線程安全就好了。

ConcurrentLinkedQueue 應該算是在高併發環境中性能最好的隊列了。它之所有能有很好的性能,是因爲其內部複雜的實現。

阻塞隊列:BlockingQueue

使用阻塞算法的隊列可以用一個鎖(入隊和出隊用同一把鎖)或兩個鎖(入隊和出隊用不同的鎖)等方式來實現。

Java併發編程實戰——線程池ThreadPoolExecutor實現原理一節中,線程池的緩衝隊列就是由BlockingQueue實現的。我也簡單介紹了,BlockingQueue被廣泛使用在“生產者-消費者”問題中,因爲它的put()take()兩個函數,就是天然的生產者消費者問題的方案。這兩個函數使用一個鎖Lock,兩個Condition來完成生產和消費的配合。具體可以參考原文,我也貼出了代碼來。

下面主要介紹一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,這三個 BlockingQueue 的實現類。

ArrayBlockingQueue

ArrayBlockingQueue 是 BlockingQueue 接口的有界隊列實現類,底層採用數組來實現。ArrayBlockingQueue 一旦創建,容量不能改變。其併發控制採用可重入鎖來控制,不管是插入操作還是讀取操作,都需要獲取到鎖才能進行操作。
ArrayBlockingQueue 默認情況下不能保證線程訪問隊列的公平性,也可以設置成爲公平鎖,比如用下面的代碼:

ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);

LinkedBlockingQueue

LinkedBlockingQueue 底層基於單向鏈表實現的阻塞隊列,可以當做無界隊列也可以當做有界隊列來使用,同樣滿足 FIFO 的特性,與 ArrayBlockingQueue 相比起來具有更高的吞吐量,爲了防止 LinkedBlockingQueue 容量迅速增,損耗大量內存。通常在創建 LinkedBlockingQueue 對象時,會指定其大小,如果未指定,容量等於 Integer.MAX_VALUE。

PriorityBlockingQueue

PriorityBlockingQueue 是一個支持優先級的無界阻塞隊列。默認情況下元素採用自然順序進行排序,也可以通過自定義類實現 compareTo() 方法來指定元素排序規則,或者初始化時通過構造器參數 Comparator 來指定排序規則。

PriorityBlockingQueue 併發控制採用的是 ReentrantLock,隊列爲無界隊列(ArrayBlockingQueue 是有界隊列,LinkedBlockingQueue 也可以通過在構造函數中傳入 capacity 指定隊列最大的容量,但是 PriorityBlockingQueue 只能指定初始的隊列大小,後面插入元素的時候,如果空間不夠的話會自動擴容)。

簡單地說,它就是 PriorityQueue 的線程安全版本。不可以插入 null 值,同時,插入隊列的對象必須是可比較大小的(comparable),否則報 ClassCastException 異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章