Java ConcurrentHashmap 解析
總體描述:
concurrentHashmap是為了高併發而實現,內部採用分離鎖的設計,有效地避開了熱點訪問。而對於每個分段,ConcurrentHashmap採用final和記憶體可見修飾符Volatile關鍵字(記憶體立即可見:Java 的記憶體模型可以保證:某個寫執行緒對 value 域的寫入馬上可以被後續的某個讀執行緒“看”到。注:並不能保證對volatile變數狀態有依賴的其他操作的原子性)
借用某部落格對concurrentHashmap對結構圖:
不難看出,concurrenthashmap採用了二次hash的方式,第一次hash將key對映到對應的segment,而第二次hash則是對映到segment的不同桶中。
為什麼要用二次hash,主要原因是為了構造分離鎖,使得對於map的修改不會鎖住整個容器,提高併發能力。當然,沒有一種東西是絕對完美的,二次hash帶來的問題是整個hash的過程比hashmap單次hash要長,所以,如果不是併發情形,不要使用concurrentHashmap。
程式碼實現:
該資料結構中,最核心的部分是兩個內部類,HashEntry和Segment
concurrentHashmap維護一個segment陣列,將元素分成若干段(第一次hash)
/** * The segments, each of which is a specialized hash table. */ final Segment<K,V>[] segments;
segments的每一個segment維護一個連結串列陣列
程式碼:
再來看看構造方法
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift; this.segmentMask = ssize - 1; if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; // create segments and segments[0] Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
程式碼28行,一旦指定了concurrencyLevel(segments陣列大小)便不能改變,這樣,一旦threshold超標,rehash真不會影響segments陣列,這樣,在大併發的情況下,只會影響某一個segment的rehash而其他segment不會受到影響
(put方法都要上鎖)
HashEntry
與hashmap類似,concurrentHashmap也採用了連結串列作為每個hash桶中的元素,不過concurrentHashmap又有些不同
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; } /** * Sets next field with volatile write semantics. (See above * about use of putOrderedObject.) */ final void setNext(HashEntry<K,V> n) { UNSAFE.putOrderedObject(this, nextOffset, n); } // Unsafe mechanics static final sun.misc.Unsafe UNSAFE; static final long nextOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class k = HashEntry.class; nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } }
HashEntry的key,hash採用final,可以避免併發修改問題,HashEntry鏈的尾部是不能修改的,而next和value採用volatile,可以避免使用同步造成的併發效能災難,新版(jdk1.7)的concurrentHashmap大量使用java Unsafe類提供的原子操作,直接呼叫底層作業系統,提高效能(這塊我也不是特別清楚)
get方法(1.6 vs 1.7)
1.6
V get(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K,V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) { V v = e.value; if (v != null) return v; return readValueUnderLock(e); // recheck } e = e.next; } } return null; }
1.6的jdk採用了樂觀鎖的方式處理了get方法,在get的時候put方法正在new物件,而此時value並未賦值,這時判斷為空則加鎖訪問
1.7
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; }
1.7並沒有判斷value=null的情況,不知為何
跟同事溝通過,無論是1.6還是1.7的實現,實際上都是一種樂觀的方式,而樂觀的方式帶來的是效能上的提升,但同時也帶來資料的弱一致性,如果你的業務是強一致性的業務,可能就要考慮另外的解決辦法(用Collections包裝或者像jdk6中一樣二次加鎖獲取)
http://ifeve.com/concurrenthashmap-weakly-consistent/
這篇文章可以很好地解釋弱一致性問題
put方法
public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); 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 s = ensureSegment(j); return s.put(key, hash, value, false); }
對於put,concurrentHashmap採用自旋鎖的方式,不同於1.6的直接獲取鎖
注:個人理解,這裡採用自旋鎖可能作者是覺得在分段鎖的狀態下,併發的可能本來就比較小,並且鎖佔用時間又並不是特別長,因此自旋鎖可以減小執行緒喚醒和切換的開銷
關於hash
private int hash(Object k) { int h = hashSeed; if ((0 != h) && (k instanceof String)) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
concurrentHashMap採用本身hashcode的同時,採用Wang/Jenkins演算法對每位都做了處理,使得發生hash衝突的可能性大大減小(否則效率會很差)
而對於concurrentHashMap,segments的大小在初始時確定,此後不變,而元素所在segments桶序列由hash的高位決定
public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); 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 s = ensureSegment(j); return s.put(key, hash, value, false); }
segmentShift為(32-segments大小的二進位制長度)
總結
concurrentHashmap主要是為併發設計,與Collections的包裝不同,他不是採用全同步的方式,而是採用非鎖get方式,通過資料的弱一致性帶來效能上的大幅提升,同時採用分段鎖的策略,提高併發能力
相關文章
- ConcurrentHashMap原始碼解析-Java7HashMap原始碼Java
- JAVA集合:ConcurrentHashMap深度解析(版本對比)JavaHashMap
- ConcurrentHashMap原始碼解析HashMap原始碼
- Java併發指南13:Java 中的 HashMap 和 ConcurrentHashMap 全解析JavaHashMap
- Java7/8中的HashMap和ConcurrentHashMap全解析JavaHashMap
- Java ConcurrentHashMap 高併發安全實現原理解析JavaHashMap
- ConcurrentHashMap解析(JDK1.8)HashMapJDK
- Java併發集合類ConcurrentHashMap底層核心原始碼解析JavaHashMap原始碼
- Java集合--ConcurrentHashMap原理JavaHashMap
- Java併發指南13:Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析JavaHashMap
- 死磕 java集合之ConcurrentHashMap原始碼分析(二)——擴容全解析JavaHashMap原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)——插入元素全解析JavaHashMap原始碼
- Java中的集合框架深度解析:從ArrayList到ConcurrentHashMap的效能考量Java框架HashMap
- HashMap 、ConcurrentHashMap知識點全解析HashMap
- Java併發5:ConcurrentHashMapJavaHashMap
- Java併發之ConcurrentHashMapJavaHashMap
- Java中ConcurrentHashMap學習JavaHashMap
- Java 中 ConcurrentHashMap 原理分析JavaHashMap
- Java ConcurrentHashMap (Java程式碼實戰-005)JavaHashMap
- Java併發——ConcurrentHashMap(JDK 1.8)JavaHashMapJDK
- [Java併發]Concurrenthashmap的size()JavaHashMap
- Java併發包原始碼學習系列:JDK1.8的ConcurrentHashMap原始碼解析Java原始碼JDKHashMap
- 【Java】ConcurrentHashMap執行緒安全技巧JavaHashMap執行緒
- ConcurrentHashMap原始碼解析,多執行緒擴容HashMap原始碼執行緒
- 死磕 java集合之ConcurrentHashMap原始碼分析(三)——刪除元素全解析(內含彩蛋)JavaHashMap原始碼
- Java7 ConcurrentHashMap原始碼淺析JavaHashMap原始碼
- 帶你走進Java集合之ConcurrentHashMapJavaHashMap
- 原始碼淺入淺出 Java ConcurrentHashMap原始碼JavaHashMap
- Java集合之ConcurrentHashMap原始碼淺析JavaHashMap原始碼
- ConcurrentHashMapHashMap
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)JavaHashMap原始碼
- ConcurrentHashMap原理HashMap
- 一文讓你徹底理解 Java HashMap 和 ConcurrentHashMapJavaHashMap
- 圖解ConcurrentHashMap圖解HashMap
- concurrentHashMap詳解HashMap
- 什麼是ConcurrentHashMap?不同JDK下ConcurrentHashMap的區別?HashMapJDK
- Java解析Excel例項解析JavaExcel
- ConcurrentHashMap 原始碼分析HashMap原始碼