一、關於分段鎖
1.分段鎖發展概況
集合框架很大程度減少了java程序員的重復勞動。在Java多線程環境中,以線程安全的方式使用集合類是一個首先考慮的問題。
能夠保證線程安全的哈希表中,ConcurrentHashMap是大家都熟知的,也知道它內部使用了分段鎖。然而,進入到Java8時代,分段鎖成為了歷史。
2.新版本ConcurrentHashMap
在Java8的ConcurrentHashMap中,分段鎖僅用來處理對象流。
Java7中,Segment繼承於ReentrantLock使用了顯示鎖,在Segment的實例方法中,每個更新操作內部又使用Unsafe來處理更新。這顯然是一種浪費。顯示鎖、Unsafe 這二者都是可以保證對對象的原子操作。使用一個即可。
Java7中的數據存儲在數組 final Segment<K,V>[] segments; 這個一個特定大小的Segment數組,
Segment繼承於ReentrantLock 故而可以在更新操作時使用顯示鎖。
二、Java7的ConcurrentHashMap
java7中,ConcurrentHashMap的內部類Segment.
Segment繼承了ReetrantLock,利用了顯示鎖,同時在更新操作中也使用了Unsafe.雙管齊下來保證線程安全。
static final class Segment<K,V> extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; // tryLock()最多等待時間 static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; // 分段表,元素通過entryAt/setEntryAt這兩個方法提供了瞬時操作。 transient volatile HashEntry<K,V>[] table; // 元素數量,通過鎖或可見性地瞬時讀取 transient int count; // 修改次數 transient int modCount; // 表大小超出threshold時,重新哈希運算。它的值 = (int)(capacity*loadFactor) transient int threshold; // 負載因子 final float loadFactor; final V put(K key, int hash, V value, boolean onlyIfAbsent) { 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; if (c > threshold && tab.length < MAXIMUM_CAPACITY) rehash(node); else setEntryAt(tab, index, node); ++modCount; count = c; oldValue = null; break; } } } finally { unlock(); } return oldValue; } /** 移除:value為null時,只匹配key,否則,兩個都匹配 */ final V remove(Object key, int hash, Object value) { if (!tryLock()) scanAndLock(key, hash); V oldValue = null; try { HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash; HashEntry<K,V> e = entryAt(tab, index); HashEntry<K,V> pred = null; while (e != null) { K k; HashEntry<K,V> next = e.next; if ((k = e.key) == key || (e.hash == hash && key.equals(k))) { V v = e.value; if (value == null || value == v || value.equals(v)) { if (pred == null) // 這里是cas操作 setEntryAt(tab, index, next); else pred.setNext(next); ++modCount; --count; oldValue = v; } break; } pred = e; e = next; } } finally { unlock(); } return oldValue; } final void clear() { lock(); try { HashEntry<K,V>[] tab = table; for (int i = 0; i < tab.length ; i++) // Unsafe 操作 setEntryAt(tab, i, null); ++modCount; count = 0; } finally { unlock(); } } }
三、Java8的ConcurrentHashMap
Java8中,ConcurrentHashMap較之前版本有了很大的改變。
使用Node數組替代了Segment數組來存儲數據。Node數組中不再使用顯示鎖,而是Unsafe的樂觀鎖機制。
Segment予以保留,僅用來處理對象流的讀寫。
從如下Java8版本的ConcurrentHashMap$Segment源碼來看,分段鎖,基本棄用了。
static class Segment<K,V> extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; final float loadFactor; Segment(float lf) { this.loadFactor = lf; } }