天天看點

Java -- 每日一問:如何保證集合是線程安全的? ConcurrentHashMap如何實作高效地線程安全?

Java -- 每日一問:如何保證集合是線程安全的? ConcurrentHashMap如何實作高效地線程安全?

典型回答

Java 提供了不同層面的線程安全支援。在傳統集合架構内部,除了 Hashtable 等同步容器,還提供了所謂的同步包裝器(Synchronized Wrapper),我們可以調用 Collections 工具類提供的包裝方法,來擷取一個同步的包裝容器(如 Collections.synchronizedMap),但是它們都是利用非常粗粒度的同步方式,在高并發情況下,性能比較低下。

另外,更加普遍的選擇是利用并發包提供的線程安全容器類,它提供了:

  • 各種并發容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。
  • 各種線程安全隊列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。
  • 各種有序容器的線程安全版本等。

具體保證線程安全的方式,包括有從簡單的 synchronize 方式,到基于更加精細化的,比如基于分離鎖實作的 ConcurrentHashMap 等并發實作等。具體選擇要看開發的場景需求,總體來說,并發包内提供的容器通用場景,遠優于早期的簡單同步實作。

高手回答

1.7

put加鎖

通過分段加鎖segment,一個hashmap裡有若幹個segment,每個segment裡有若幹個桶,桶裡存放K-V形式的連結清單,put資料時通過key哈希得到該元素要添加到的segment,然後對segment進行加鎖,然後在哈希,計算得到給元素要添加到的桶,然後周遊桶中的連結清單,替換或新增節點到桶中

size

分段計算兩次,兩次結果相同則傳回,否則對是以段加鎖重新計算

1.8

put CAS 加鎖

1.8中不依賴與segment加鎖,segment數量與桶數量一緻;

首先判斷容器是否為空,為空則進行初始化利用volatile的sizeCtl作為互斥手段,如果發現競争性的初始化,就暫停在那裡,等待條件恢複,否則利用CAS設定排他标志(U.compareAndSwapInt(this, SIZECTL, sc, -1));否則重試

對key hash計算得到該key存放的桶位置,判斷該桶是否為空,為空則利用CAS設定新節點

否則使用synchronize加鎖,周遊桶中資料,替換或新增加點到桶中

最後判斷是否需要轉為紅黑樹,轉換之前判斷是否需要擴容