天天看點

[Java并發程式設計] 并發容器架構的簡單介紹

三軍可奪帥也,匹夫不可奪志也。———《論語》

上一篇講到同步容器類的潛在問題,可以通過兩個方法解決。

  1. 可以通過用戶端加鎖解決。
  2. 可以使用并發容器類來解決問題。

用戶端加鎖的方法我們已經知道,是以,這一篇介紹一下并發容器類原理,看它是如何解決這些問題的。

下面看下并發容器的架構圖:

這裡寫圖檔描述

我們從上圖可以看到,它們分為五大類:Map, List, Set,Collection,Queue, 同步容器類都是從這五大基類繼承而來,它們都是線程安全的。

同步容器類實作線程安全性的方式是所有方法都持有一個鎖,并發容器則不同,這裡主要講介紹幾個并發容器類來說明。

ConcurrentHashMap

它并不是每個方法都在同一個鎖上實作同步使得每次隻能有一個線程通路容器。而是使用一種粒度更細的加鎖機制來實作更大程度的共享,這種機制稱為分段鎖。在這種機制中,任意數量的讀取線程可以并發通路 Map,執行讀取操作的線程和執行寫入操作的線程可以并發的通路 Map,并且一定數量的寫入線程可以并發的修改 Map。是以在并發環境中,它實作更高的吞吐量,在單線程環境中損失非常小的性能。

由于 ConcurrentHashMap 不能加鎖來執行獨占通路,是以我們無法通過用戶端加鎖來建立新的原子操作。然而,一些常見的複合操作已經實作了。包括如下接口:

當你需要這些額外的原子操作時,那麼你可以考慮使用它。

CopyOnWriteArrayList

寫時複制容器,可以了解為向容器添加一個元素時,先将目前的容器進行複制,生成一個新的容器,然後在向新的容器添加元素,之後再将原容器的引用指向新的容器。

好處是,對 CopyOnWrite 容器可以不用加鎖進行并發的讀,因為此時不會添加任何元素。CopyOnWrite 是一種讀寫分離的思想,讀操作和寫操作的是不同的容器。

它可以用于替代同步 List,但是每次在修改容器時都會複制底層數組。比如 add(),set() 等操作時,需要一定的開銷,特别是當容器規模較大時,它比較适用于讀多寫少的并發場景。

ArrayBlockingQueue

它是數組實作的線程安全的有界的阻塞隊列(FIFO),支援多任務并發操作。它内部通過互斥鎖保護競争資源,實作了多線程對競争資源的互斥通路;有界是指數組的長度是固定的;阻塞是指當競争資源已經某線程擷取時,其他要擷取該資源的線程需要阻塞等待。

它的核心函數都是通過可重入鎖 ReentrantLock 來確定線程同步的。包括 put(),offer(),take(),poll() 等。同時,還包含兩個條件 Condition(notEmpty, notFull),加入元素是,如果隊列已滿則必須等待;取出元素時,如果隊列為空則必須等待。

總結

并發容器是針對多個線程并發通路設計的,通過并發容器來代替同步容器,可以極大的提高伸縮性并降低風險。并發容器提供的疊代不會抛出 ConcurrentModificationException,是以在疊代過程中不需要對容器加鎖。另外,并發容器隻能保證資料的最終一緻性,不能保證明時一緻性。換句話說,容器被修改後的資料并不保證能夠實時的反應到疊代器的周遊。

參考