14個Java並發容器,你用過幾個?


不考慮多線程並發的情況下,容器類一般使用 ArrayList、HashMap 等線程不安全的類,效率更高。在並發場景下,常會用到 ConcurrentHashMap、ArrayBlockingQueue 等線程安全的容器類,雖然犧牲了一些效率,但卻得到了安全。

上面提到的線程安全容器都在 java.util.concurrent 包下,這個包下並發容器不少,今天全部翻出來鼓搗一下。

僅做簡單介紹,后續再分別深入探索。

  1. ConcurrentHashMap:並發版 HashMap

  2. CopyOnWriteArrayList:並發版 ArrayList

  3. CopyOnWriteArraySet:並發 Set

  4. ConcurrentLinkedQueue:並發隊列 (基於鏈表)

  5. ConcurrentLinkedDeque:並發隊列 (基於雙向鏈表)

  6. ConcurrentSkipListMap:基於跳表的並發 Map

  7. ConcurrentSkipListSet:基於跳表的並發 Set

  8. ArrayBlockingQueue:阻塞隊列 (基於數組)

  9. LinkedBlockingQueue:阻塞隊列 (基於鏈表)

  10. LinkedBlockingDeque:阻塞隊列 (基於雙向鏈表)

  11. PriorityBlockingQueue:線程安全的優先隊列

  12. SynchronousQueue:讀寫成對的隊列

  13. LinkedTransferQueue:基於鏈表的數據交換隊列

  14. DelayQueue:延時隊列

1.ConcurrentHashMap 並發版 HashMap

最常見的並發容器之一,可以用作並發場景下的緩存。底層依然是哈希表,但在 JAVA 8 中有了不小的改變,而 JAVA 7 和 JAVA 8 都是用的比較多的版本,因此經常會將這兩個版本的實現方式做一些比較(比如面試中)。

一個比較大的差異就是,JAVA 7 中采用分段鎖來減少鎖的競爭,JAVA 8 中放棄了分段鎖,采用 CAS(一種樂觀鎖),同時為了防止哈希沖突嚴重時退化成鏈表(沖突時會在該位置生成一個鏈表,哈希值相同的對象就鏈在一起),會在鏈表長度達到閾值(8)后轉換成紅黑樹(比起鏈表,樹的查詢效率更穩定)。

2.CopyOnWriteArrayList 並發版 ArrayList

並發版 ArrayList,底層結構也是數組,和 ArrayList 不同之處在於:當新增和刪除元素時會創建一個新的數組,在新的數組中增加或者排除指定對象,最后用新增數組替換原來的數組。

適用場景:由於讀操作不加鎖,寫(增、刪、改)操作加鎖,因此適用於讀多寫少的場景。

局限:由於讀的時候不會加鎖(讀的效率高,就和普通 ArrayList 一樣),讀取的當前副本,因此可能讀取到臟數據。如果介意,建議不用。

看看源碼感受下:

3.CopyOnWriteArraySet 並發 Set

基於 CopyOnWriteArrayList 實現(內含一個 CopyOnWriteArrayList 成員變量),也就是說底層是一個數組,意味着每次 add 都要遍歷整個集合才能知道是否存在,不存在時需要插入(加鎖)。

適用場景:在 CopyOnWriteArrayList 適用場景下加一個,集合別太大(全部遍歷傷不起)。

4.ConcurrentLinkedQueue 並發隊列 (基於鏈表)

基於鏈表實現的並發隊列,使用樂觀鎖 (CAS) 保證線程安全。因為數據結構是鏈表,所以理論上是沒有隊列大小限制的,也就是說添加數據一定能成功。

5.ConcurrentLinkedDeque 並發隊列 (基於雙向鏈表)

基於雙向鏈表實現的並發隊列,可以分別對頭尾進行操作,因此除了先進先出 (FIFO),也可以先進后出(FILO),當然先進后出的話應該叫它棧了。

6.ConcurrentSkipListMap 基於跳表的並發 Map

SkipList 即跳表,跳表是一種空間換時間的數據結構,通過冗余數據,將鏈表一層一層索引,達到類似二分查找的效果

7.ConcurrentSkipListSet 基於跳表的並發 Set

類似 HashSet 和 HashMap 的關系,ConcurrentSkipListSet 里面就是一個 ConcurrentSkipListMap,就不細說了。

8.ArrayBlockingQueue 阻塞隊列 (基於數組)

基於數組實現的可阻塞隊列,構造時必須制定數組大小,往里面放東西時如果數組滿了便會阻塞直到有位置(也支持直接返回和超時等待),通過一個鎖 ReentrantLock 保證線程安全。

乍一看會有點疑惑,讀和寫都是同一個鎖,那要是空的時候正好一個讀線程來了不會一直阻塞嗎?

答案就在 notEmpty、notFull 里,這兩個出自 lock 的小東西讓鎖有了類似 synchronized + wait + notify 的功能。傳送門 → 終於搞懂了 sleep/wait/notify/notifyAll

9.LinkedBlockingQueue 阻塞隊列 (基於鏈表)

基於鏈表實現的阻塞隊列,想比與不阻塞的 ConcurrentLinkedQueue,它多了一個容量限制,如果不設置默認為 int 最大值。

10.LinkedBlockingDeque 阻塞隊列 (基於雙向鏈表)

類似 LinkedBlockingQueue,但提供了雙向鏈表特有的操作。

11.PriorityBlockingQueue 線程安全的優先隊列

構造時可以傳入一個比較器,可以看做放進去的元素會被排序,然后讀取的時候按順序消費。某些低優先級的元素可能長期無法被消費,因為不斷有更高優先級的元素進來。

12.SynchronousQueue 數據同步交換的隊列

一個虛假的隊列,因為它實際上沒有真正用於存儲元素的空間,每個插入操作都必須有對應的取出操作,沒取出時無法繼續放入。

import java.util.concurrent.SynchronousQueue;

public class Main {
    
    public static void main(String[] args) {
        SynchronousQueue<Integer> queue = new SynchronousQueue<>();
        new Thread(()->{
            try{
                for(int i=0;;i++){
                    System.out.println("放入:" + i);
                    queue.put(i);
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try{
                while(true){
                    System.out.println("取出:" + queue.take());
                    Thread.sleep((long)(Math.random()*2000));
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }).start();
    }
}

運行結果:

取出:0
放入:0
取出:1
放入:1
放入:2
取出:2
取出:3
放入:3
取出:4
放入:4
...
...

可以看到,寫入的線程沒有任何 sleep,可以說是全力往隊列放東西,而讀取的線程又很不積極,讀一個又 sleep 一會。輸出的結果卻是讀寫操作成對出現。

JAVA 中一個使用場景就是 Executors.newCachedThreadPool(),創建一個緩存線程池。

13.LinkedTransferQueue 基於鏈表的數據交換隊列

實現了接口 TransferQueue,通過 transfer 方法放入元素時,如果發現有線程在阻塞在取元素,會直接把這個元素給等待線程。如果沒有人等着消費,那么會把這個元素放到隊列尾部,並且此方法阻塞直到有人讀取這個元素。和 SynchronousQueue 有點像,但比它更強大。

14.DelayQueue 延時隊列

可以使放入隊列的元素在指定的延時后才被消費者取出,元素需要實現 Delayed 接口。

總結

上面簡單介紹了 JAVA 並發包下的一些容器類,知道有這些東西,遇到合適的場景時就能想起有個現成的東西可以用了。想要知其所以然,后續還得再深入探索一番。


“不積跬步,無以至千里”,希望未來的你能:有夢為馬 隨處可棲!加油,少年!

關注公眾號:「Java 知己」,每天更新Java知識哦,期待你的到來!

  • 發送「Group」,與 10 萬程序員一起進步。
  • 發送「面試」,領取BATJ面試資料、面試視頻攻略。
  • 發送「玩轉算法」,領取《玩轉算法》系列視頻教程。
  • 千萬不要發送「1024」...

每日福利


免責聲明!

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



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