日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CurrentHashMap源码剖析

發布時間:2024/4/14 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CurrentHashMap源码剖析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

什么是concurrenthashmap

concurrenthashmap(簡稱chm) 是java1.5新引入的java.util.concurrent包的成員,作為hashtable的替代。為什么呢,hashtable采用了同步整個方法的結構。雖然實現了線程安全但是性能也就大大降低了 而hashmap呢,在并發情況下會很容易出錯。所以也促進了安全并且能在多線程中使用的concurrenthashmap

如何實現concurrenthashmap

首先來看看構造方法吧
這是最底層的構造方法

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 argumentsint sshift = 0;int ssize = 1;while (ssize < concurrencyLevel) {++sshift;//代表ssize轉換的次數ssize <<= 1;}this.segmentShift = 32 - sshift;//目前不知道有什么用,是在后來的segment定位中使用this.segmentMask = ssize - 1;//segment定位使用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;}

這里我想和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源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。