上篇已經分析了HashMap在多線程環境下死循環的原因,HashTable使用synchronized來保證線程安全,但是相對來說效率低下,而ConcurrentHashMap是線程安全且高效的HashMap,這第一篇我們來看看ConcurrentHashMap
這篇同樣使用上一篇的java環境:
ConcurrentHashMap的結構
ConcurrentHashMap中包含Segment數組,Segment中包含HashEntry數組。
Segment結構
源碼如下:
final Segment<K,V>[] segments;
...
static final class Segment<K,V> extends ReentrantLock implements Serializable {
...
transient volatile HashEntry<K,V>[] table;
...
}
Segment繼承了ReentrantLock,ReentrantLock是一個可重入的互斥鎖,ReentrantLock的詳情以后有時間再聊,在這里簡單說一下,ReentrantLock實現了Lock接口,從Java官方API中粘過來說明:A reentrant mutual exclusion Lock
with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.在這里翻一下(英文不好,強行使用百度翻譯加上自己組織):一個可重入的互斥鎖,和關鍵詞synchronized修飾的方法與語句訪問隱式監視鎖(可能翻譯錯了,就理解為和synchronized具有相同作用吧)具有相同的功能和語義,但具有擴展功能,翻譯完畢。那么我們可以分析出Segment在ConcurrentHashMap作為鎖,保證了ConcurrentHashMap的線程安全。
HashEntry結構
HashEntry是一個單鏈表結構,使用HashEntry<K,V>鍵值對存數據,next節點指向下一個節點,與HashMap不同的是,存值的value變量、存下一個節點的變量next使用了關鍵字volatile(volatile 自行百度吧)修飾,源碼如下:
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
...
}
分段鎖技術保證了線程安全并提高了ConcurrentHashMap的并發訪問率
通過分析了Segment、HashEntry結構與源碼,可以得出,因為Segment繼承了ReentrantLock,所以ConcurrentHashMap使用了分段鎖的技術。把數據分為一段一段,也就是Segment數組,每個Segment中都有一個HashEntry數組,當對HashEntry進行數據存與讀的時候,先要獲取與之相對應的Segment鎖,這樣當多線程環境下,一個線程獲得鎖,訪問這一段的數據的時候,其他線程也可以訪問其他段的數據,所以保證了線程安全的同時提高了ConcurrentHashMap的并發訪問率。
ConcurrentHashMap的put、get方法
分析一下ConcurrentHashMap的put、get方法源碼
put方法
先算出數據要存到哪段中,通過算法去定位Segment,然后調用Segment對象的put方法去存儲數據
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
//hash算法算出key的哈希值
int hash = hash(key);
//通過算法算出數據該存到哪一段上
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//定位Segment數組中的Segment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
再來看Segment的put方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//先對當前的Segment的進行加鎖,保證線程安全
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
//判斷是否需要HashEntry是否需要擴容
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
一進到Segment的put方法,進行了加鎖保證了線程安全,再添加的過程中,判斷是否需要擴容,擴容過程中也會進行數據轉存,但是已經進行了加鎖,所以不會再發生HashMap中死循環的現象,而且,擴容不是針對于整個ConcurrentHashMap容器的擴容,而是針對于某個Segment中的HashEntry數組進行了擴容,這樣提高了ConcurrentHashMap的效率。
get方法
先通過算法去定位Segment,然后在通再算法定位元素
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
get過程沒有加鎖,保證了高效,但是怎么保證線程安全的呢?記得上面分析Segment結構與HashEntry結構的時候,Segment中HashEntry數組變量table使用了volatile修飾,HashEntry中用來存值的value變量也使用了volatile修飾,保證了table變量與value變量在線程間的相互可見性,就算是多個線程修改了HashEntry中的value,get方法也能讀取到value在內存中的最新值,所以既保證了線程安全又保證了高效。
ConcurrentHashMap的實現原理與使用是說完了。
歡迎大家來交流,指出文中一些說錯的地方,希望大家多多提出,讓我加深認識。
謝謝大家!