ConcurrentHashMap 在默認並發級別下會創建16個Segment對象的數組,如果鍵能均勻散列,每個 Segment 大約守護整個散列表中桶總數的 1/16。
在Segment類中,count 變數是一個計數器,它表示每個 Segment 對象管理的 table 數組包含的 HashEntry 對象的個數,也就是 Segment 中包含的 HashEntry 對象的總數。特別需要注意的是,之所以在每個 Segment 對象中包含一個計數器,而不是在 ConcurrentHashMap 中使用全局的計數器,是對 ConcurrentHashMap 並發性的考慮:因為這樣當需要更新計數器時,不用鎖定整個ConcurrentHashMap
基本元素:HashEntry
與HashMap中的Entry類似,HashEntry也包括同樣的四個域,分別是key、hash、value和next。不同的是,在HashEntry類中,key,hash和next域都被聲明為final的,value域被volatile所修飾,因此HashEntry對象幾乎是不可變的,這是ConcurrentHashmap讀操作並不需要加鎖的一個重要原因
由於value域被volatile修飾,所以其可以確保被讀線程讀到最新的值,這是ConcurrentHashmap讀操作並不需要加鎖的另一個重要原因。
ConcurrentHashMap 的構造函數
1,ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
該構造函數意在構造一個具有指定容量、指定負載因子和指定段數目/並發級別(若不是2的冪次方,則會調整為2的冪次方)的空ConcurrentHashMap
2,ConcurrentHashMap(int initialCapacity, float loadFactor)
該構造函數意在構造一個具有指定容量、指定負載因子和默認並發級別(16)的空ConcurrentHashMap
3,ConcurrentHashMap(int initialCapacity)
該構造函數意在構造一個具有指定容量、默認負載因子(0.75)和默認並發級別(16)的空ConcurrentHashMap
4,ConcurrentHashMap()
該構造函數意在構造一個具有默認初始容量(16)、默認負載因子(0.75)和默認並發級別(16)的空ConcurrentHashMap
5,ConcurrentHashMap(Map<? extends K, ? extends V> m)
該構造函數意在構造一個與指定 Map 具有相同映射的 ConcurrentHashMap,其初始容量不小於 16 (具體依賴於指定Map的大小),負載因子是 0.75,並發級別是 16, 是 Java Collection Framework 規範推薦提供的
小結
在這裡,我們提到了三個非常重要的參數:初始容量 、負載因子 和 並發級別 ,這三個參數是影響ConcurrentHashMap性能的重要參數
ConcurrentHashMap 的數據結構
通過使用段(Segment)將ConcurrentHashMap劃分為不同的部分,ConcurrentHashMap就可以使用不同的鎖來控制對哈希表的不同部分的修改,從而允許多個修改操作並發進行, 這正是ConcurrentHashMap鎖分段技術的核心內涵
ConcurrentHashMap 的並發存取
在ConcurrentHashMap中,線程對映射表做讀操作時,一般情況下不需要加鎖就可以完成,對容器做結構性修改的操作(比如,put操作、remove操作等)才需要加鎖。
用分段鎖機制實現多個線程間的並發寫操作: put(key, vlaue)
在ConcurrentHashMap中,典型結構性修改操作包括put、remove和clear,下面我們首先以put操作為例說明對ConcurrentHashMap做結構性修改的過程
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
int hash = hash(key.hashCode());
return segmentFor(hash).put(key, hash, value, false);
}
ConcurrentHashMap不同於HashMap,它既不允許key值為null,也不允許value值為null
定位段的segmentFor()方法源碼如下
final Segment<K,V> segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}
根據key的hash值的高n位就可以確定元素到底在哪一個Segment中
段的put()方法的源碼如下所示
V put( K key, int hash, V value, boolean onlyIfAbsent )
{
lock(); /* 上鎖 */
try {
int c = count;
if ( c++ > threshold ) /* ensure capacity */
rehash();
HashEntry<K, V>[] tab = table; /* table是Volatile的 */
int index = hash & (tab.length - 1); /* 定位到段中特定的桶 */
HashEntry<K, V> first = tab[index]; /* first指向桶中鏈表的表頭 */
HashEntry<K, V> e = first;
/* 檢查該桶中是否存在相同key的結點 */
while ( e != null && (e.hash != hash || !key.equals( e.key ) ) )
e = e.next;
V oldValue;
if ( e != null ) /* 該桶中存在相同key的結點 */
{
oldValue = e.value;
if ( !onlyIfAbsent )
e.value = value; /* 更新value值 */
}else { /* 該桶中不存在相同key的結點 */
oldValue = null;
++modCount; /* 結構性修改,modCount加1 */
tab[index] = new HashEntry<K, V>( key, hash, first, value ); /* 創建HashEntry並將其鏈到表頭 */
count = c; /* write-volatile,count值的更新一定要放在最後一步(volatile變數) */
}
return(oldValue); /* 返回舊值(該桶中不存在相同key的結點,則返回null) */
} finally {
unlock(); /* 在finally子句中解鎖 */
}
}
相比較於 HashTable 和由同步包裝器包裝的HashMap每次只能有一個線程執行讀或寫操作,ConcurrentHashMap 在並發訪問性能上有了質的提高。在理想狀態下,ConcurrentHashMap 可以支持 16 個線程執行並發寫操作(如果並發級別設置為 16),及任意數量線程的讀操作
最後
如果你是一名想要或準備要往架構師方向發展的java程序員 、如果你想要關於java架構學習的視頻資料和BAT的面試題及答案的 。請私信我【資料】 ,我會將(分散式架構、高可擴展、高性能、高並發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分散式項目實戰學習架構師視頻)免費分享哦!
獲取資料的方式:轉發+私信【資料】領取!
推薦閱讀: