CurrentHashMap源码剖析
什么是concurrenthashmap
concurrenthashmap(簡稱chm) 是java1.5新引入的java.util.concurrent包的成員,作為hashtable的替代。為什么呢,hashtable采用了同步整個方法的結構。雖然實現了線程安全但是性能也就大大降低了 而hashmap呢,在并發情況下會很容易出錯。所以也促進了安全并且能在多線程中使用的concurrenthashmap
如何實現concurrenthashmap
首先來看看構造方法吧
這是最底層的構造方法
這里我想和hashmap對比來分析,因為他們長得很像,hashmap是entry<K,v>[],而chm就是segments<K,v>[].可以說每一個segment都是一個hashmap,想要進入segment還需要獲取對應的鎖。默認conccurrenthashmap的segment數是16.每個segment內的hashentry數組大小也是16個。threadshord是16*0.75
chm如何定位
先看看chm的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);}這里對key的hash值再哈希了一次。使用的方法是wang/jenkins的哈希算法,這里再hash是為了減少hash沖突。如果不這樣做的話,會出現大多數值都在一個segment上,這樣就失去了分段鎖的意義。
以上代碼只是算出了key的新的hash值,但是怎么用這個hash值定位呢
-
如果我們要取得一個值,首先我們肯定需要先知道哪個segment,然后再知道hashentry的index,最后一次循環遍歷該index下的元素
確定segment,:(h >>> segmentShift) & segmentMask。默認使用h的前4位,segmentMask為15確定index:(tab.length - 1) & h hashentry的長度減1,用之前確定了sement的新h計算循環:for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next)比較:if ((k = e.key) == key || (e.hash == h && key.equals(k)))return e.value;
chm取得元素
public V get(Object key) {Segment<K,V> s; // manually integrate access methods to reduce overheadHashEntry<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;}如果我們要取得一個值,首先我們肯定需要先知道哪個segment,然后再知道hashentry的index,最后一次循環遍歷該index下的元素
確定segment,:(h >>> segmentShift) & segmentMask。默認使用h的前4位,segmentMask為15確定index:(tab.length - 1) & h hashentry的長度減1,用之前確定了sement的新h計算循環:for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next)比較:if ((k = e.key) == key || (e.hash == h && key.equals(k)))return e.value;chm 存放元素
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 ensureSegments = ensureSegment(j);return s.put(key, hash, value, false);} 在jdk中,native方法的實現是沒辦法看的,請下載openjdk來看。在put方法中實際是需要判斷是否需要擴容的擴容的時機選在閥值(threadshold)裝滿時,而不像hashmap是在裝入后,再判斷是否裝滿并擴容這里就是concurrenthashmap的高明之處,有可能會出現擴容后就沒有新數據的情況concrrenthashmap 容量判斷
public int size() {final Segment<K,V>[] segments = this.segments;int size;boolean overflow; // true if size overflows 32 bitslong sum; // sum of modCountslong last = 0L; // previous sumint retries = -1; // first iteration isn't retrytry {for (;;) {if (retries++ == RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)ensureSegment(j).lock(); // force creation}sum = 0L;size = 0;overflow = false;for (int j = 0; j < segments.length; ++j) {Segment<K,V> seg = segmentAt(segments, j);if (seg != null) {sum += seg.modCount;int c = seg.count;if (c < 0 || (size += c) < 0)overflow = true;}}if (sum == last)break;last = sum;}} finally {if (retries > RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)segmentAt(segments, j).unlock();}}return overflow ? Integer.MAX_VALUE : size;}這段代碼寫的真巧妙,在統計concurrenthashmap的數量時,有多線程情況,但是并不是一開始就鎖住修改結構的方法,比如put,remove等 先執行一次統計,然后在執行一次統計,如果兩次統計結果都一樣,則沒問題。反之就鎖修改結構的方法。這樣做效率會高很多,在統計的時候查詢依舊可以進行chm是否為空判斷
public boolean isEmpty() {long sum = 0L;final Segment<K,V>[] segments = this.segments;for (int j = 0; j < segments.length; ++j) {Segment<K,V> seg = segmentAt(segments, j);if (seg != null) {if (seg.count != 0)return false;sum += seg.modCount;}}if (sum != 0L) { // recheck unless no modificationsfor (int j = 0; j < segments.length; ++j) {Segment<K,V> seg = segmentAt(segments, j);if (seg != null) {if (seg.count != 0)return false;sum -= seg.modCount;}}if (sum != 0L)return false;}return true;} 即使在空的情況下也不能僅僅只靠segment的計數器來判斷,還是因為多線程,count的值隨時在變,所以追加判斷 modcount前后是否一致,如果一致,說明期間沒有修改。chm刪除元素
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)setEntryAt(tab, index, next);elsepred.setNext(next);++modCount;--count;oldValue = v;}break;}pred = e;e = next;}} finally {unlock();}return oldValue;}思考
1.hashmap的默認大小是1<<4,即16,但是concurrenthashmap卻直接16.
2.(k << SSHIFT) + SBASE 這段話我是真沒懂,定位的時候會用
3.get方法中直接寫的定位方法,為什么不像remove一樣調用segmentforhash呢
4.concurrenthashmap和hashtable不能允許key或者value為null。因為在多線程情況下無法判斷返回一個null值到底是key為null還是value為null
hashmap是非多線程的,所以可以key為null何value為null
總結
以上是生活随笔為你收集整理的CurrentHashMap源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 迎合人工智能时代 码教授开设Python
- 下一篇: DNS域名解析服务(正向解析、反向解析、