在並發編程中,有時候需要使用線程安全的隊列,如果要實現一個線程安全的隊列有兩種實現方式:阻塞算法、非阻塞算法。
使用阻塞算法的隊列可以用一個鎖(出入隊列用同一把鎖),或兩個鎖(入隊和出隊用不同的鎖),非阻塞的實現方式則可以用循環CAS的方式實現。
一 非阻塞方式實現線程安全的隊列
ConcurrentLinkedQueue
ConcurrentLinkedQueue由head節點和tail節點組成,每個節點node由節點元素item和指向下一個節點next的引用組成。當我們增加一個元素時,它會添加到隊列的末尾,當我們取一個元素時,它會返回一個隊列頭部的元素。
雖然ConcurrentLinkedQueue的性能很好,但是在調用size()方法的時候,會遍歷一遍集合,對性能損害較大,執行很慢,因此應該盡量的減少使用這個方法,如果判斷是否為空,最好用isEmpty()方法。
ConcurrentLinkedQueue不允許插入null元素,會拋出空指針異常。
ConcurrentLinkedQueue是無界的,所以使用時,一定要注意內存溢出的問題。即對並發不是很大中等的情況下使用,不然占用內存過多或者溢出,對程序的性能影響很大,甚至是致命的。
二 阻塞方式實現線程安全的對列
JDK7提供了7個阻塞隊列,如下。
口 ArrayBlockingqueue:ー個由數組結構組成的有界阻塞隊列。
口 LinkedBlockingqueue:一個由鏈表結構組成的有界阻塞隊列。
口 PriorityBlockingqueue:一個支持優先級排序的無界阻塞隊列。
口 Delayqueue:ー個使用優先級隊列實現的無界阻塞隊列。
口 Synchronousqueue:一個不存儲元素的阻塞隊列。
口 Linkedtransferqueue:ー個由鏈表結構組成的無界阻塞隊列。
口 LinkedblockingDeque:ー個由鏈表結構組成的雙向阻塞隊列。
ArrayBlockingQueue
LinkedBlockingQueue
ConcurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue 區別及使用場景
ArrayBlockingQueue extends AbstractQueue implements BlockingQueue LinkedBlockingQueue extends AbstractQueue implements BlockingQueue ConcurrentLinkedQueue extends AbstractQueue implements Queue
ConcurrentLinkedQueue基於CAS的無鎖技術,不需要在每個操作時使用鎖,所以擴展性表現要更加優異,在常見的多線程訪問場景,一般可以提供較高吞吐量。
LinkedBlockingQueue內部則是基於鎖,並提供了BlockingQueue的等待性方法。
ArrayBlockingQueue是初始容量固定的阻塞隊列
,我們可以用來作為數據庫模塊成功競拍的隊列,比如有10個商品,那么我們就設定一個10大小的數組隊列。
ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現,是一個異步隊列
,入隊的速度很快,出隊進行了加鎖,性能稍慢。
LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖
,當隊空的時候線程會暫時阻塞。
LinkedBlockingQueue與ArrayBlockingQueue的異同
相同:
1、LinkedBlockingQueue和ArrayBlockingQueue都實現了BlockingQueue接口;
2、LinkedBlockingQueue和ArrayBlockingQueue都是可阻塞的隊列
內部都是使用ReentrantLock和Condition來保證生產和消費的同步;
當隊列為空,消費者線程被阻塞;當隊列裝滿,生產者線程被阻塞;
使用Condition的方法來同步和通信:await()和signal()
不同:
1、由上圖可以看出,他們的鎖機制不同
LinkedBlockingQueue中的鎖是分離的,生產者的鎖PutLock,消費者的鎖takeLock
而ArrayBlockingQueue生產者和消費者使用的是同一把鎖;
2、他們的底層實現機制也不同
LinkedBlockingQueue內部維護的是一個鏈表結構
在生產和消費的時候,需要創建Node對象進行插入或移除,大批量數據的系統中,其對於GC的壓力會比較大
而ArrayBlockingQueue內部維護了一個數組
在生產和消費的時候,是直接將枚舉對象插入或移除的,不會產生或銷毀任何額外的對象實例
3、構造時候的區別
LinkedBlockingQueue有默認的容量大小為:Integer.MAX_VALUE,當然也可以傳入指定的容量大小
ArrayBlockingQueue在初始化的時候,必須傳入一個容量大小的值
看其提供的構造方法就能知道
4、執行clear()方法
LinkedBlockingQueue執行clear方法時,會加上兩把鎖
5、統計元素的個數
LinkedBlockingQueue中使用了一個AtomicInteger對象來統計元素的個數
ArrayBlockingQueue則使用int類型來統計元素
阻塞隊列的實現
https://segmentfault.com/a/1190000000373535
參考:
https://www.jb51.net/article/90899.htm
https://blog.csdn.net/jameshadoop/article/details/52729796