【Java容器】ConcurrentHashMap詳解

ConcurrentHashMap

1. ConcurrentHashmap是如何實現線程安全的

1. 數據結構

static final class Segment<K,V> extends ReentrantLock implements Serializable {
       private static final long serialVersionUID = 2249069246763182397L;
       
       // 和 HashMap 中的 HashEntry 作用一樣,真正存放數據的桶
       transient volatile HashEntry<K,V>[] table;
       transient int count;
       transient int modCount;
       transient int threshold;
       final float loadFactor;
}

在這裏插入圖片描述
在這裏插入圖片描述

Segment 類繼承於 ReentrantLock 類,從而使得 Segment 對象能充當鎖的角色。

本質上,ConcurrentHashMap就是一個Segment數組,而一個Segment實例則是一個小的哈希表。由於Segment類繼承於ReentrantLock類,從而使得Segment對象能充當鎖的角色,這樣,每個 Segment對象就可以守護整個ConcurrentHashMap的若干個桶,其中每個桶是由若干個HashEntry 對象鏈接起來的鏈表。

2. 讀操作的併發性

在這裏插入圖片描述

上面的HashBucket就是entries數組

HashEntry用來封裝具體的鍵值對,是個典型的四元組。與HashMap中的Entry類似,HashEntry也包括同樣的四個域,分別是key、hash、value和next。不同的是,在HashEntry類中,key,hash和next域都被聲明爲final的,value域被volatile所修飾,因此HashEntry對象幾乎是不可變的,這是ConcurrentHashmap讀操作並不需要加鎖的一個重要原因。next域被聲明爲final本身就意味着我們不能從hash鏈的中間或尾部添加或刪除節點,因爲這需要修改next引用值,因此所有的節點的修改只能從頭部開始。對於put操作,可以一律添加到Hash鏈的頭部。但是對於remove操作,可能需要從中間刪除一個節點,這就需要將要刪除節點的前面所有節點整個複製(重新new)一遍,最後一個節點指向要刪除結點的下一個結點(這在談到ConcurrentHashMap的刪除操作時還會詳述)。特別地,由於value域被volatile修飾,所以其可以確保被讀線程讀到最新的值,這是ConcurrentHashmap讀操作並不需要加鎖的另一個重要原因。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }
 }

3. 寫操作的併發性

segmentFor()方法根據傳入的hash值向右無符號右移segmentShift位,然後和segmentMask進行與操作就可以定位到特定的段。在這裏,假設Segment的數量(segments數組的長度)是2的n次方(Segment的數量總是2的倍數,具體見構造函數的實現),那麼segmentShift的值就是32-n(hash值的位數是32),而segmentMask的值就是2^n-1(寫成二進制的形式就是n個1)。進一步地,我們就可以得出以下結論:根據key的hash值的高n位就可以確定元素到底在哪一個Segment中。

final Segment<K,V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
}

2. ConcurrentHashmap的性能如何

  • 針對讀寫操作進入分析
  • 在ConcurrentHashMap中,無論是讀操作還是寫操作都能保證很高的性能:在進行讀操作時(幾乎)不需要加鎖,而在寫操作時通過鎖分段技術只對所操作的段加鎖而不影響客戶端對其它段的訪問。特別地,在理想狀態下,ConcurrentHashMap 可以支持 16 個線程執行併發寫操作(如果併發級別設爲16),及任意數量線程的讀操作。

3. JDK1.7到JDK1.8發生的變化

參考 https://my.oschina.net/hosee/blog/675884

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