摘要:因為在多線程情況下無法判斷返回一個值到底是為還是為是非多線程的,所以可以為何為
什么是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 arguments int 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] Segments0 = new Segment (loadFactor, (int)(cap * loadFactor), (HashEntry [])new HashEntry[cap]); Segment [] ss = (Segment [])new Segment[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
這里我想和hashmap對比來分析,因為他們長得很像,hashmap是entry
先看看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 (HashEntrychm取得元素e = (HashEntry ) 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;
public V get(Object key) { Segments; // manually integrate access methods to reduce overhead HashEntry [] tab; int h = hash(key); long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; if ((s = (Segment )UNSAFE.getObjectVolatile(segments, u)) != null && (tab = s.table) != null) { for (HashEntry e = (HashEntry ) 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 (HashEntrychm 存放元素e = (HashEntry ) 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;
public V put(K key, V value) { Segmentconcrrenthashmap 容量判斷s; if (value == null) throw new NullPointerException(); int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment )UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false); } 在jdk中,native方法的實現是沒辦法看的,請下載openjdk來看。在put方法中實際是需要判斷是否需要擴容的 擴容的時機選在閥值(threadshold)裝滿時,而不像hashmap是在裝入后,再判斷是否裝滿并擴容 這里就是concurrenthashmap的高明之處,有可能會出現擴容后就沒有新數據的情況
public int size() { final Segmentchm是否為空判斷[] segments = this.segments; int size; boolean overflow; // true if size overflows 32 bits long sum; // sum of modCounts long last = 0L; // previous sum int retries = -1; // first iteration isn"t retry try { 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 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等 先執行一次統計,然后在執行一次統計,如果兩次統計結果都一樣,則沒問題。反之就鎖修改結構的方法。這樣做效率會高很多,在統計的時候查詢依舊可以進行
public boolean isEmpty() { long sum = 0L; final Segmentchm刪除元素[] segments = this.segments; for (int j = 0; j < segments.length; ++j) { Segment seg = segmentAt(segments, j); if (seg != null) { if (seg.count != 0) return false; sum += seg.modCount; } } if (sum != 0L) { // recheck unless no modifications for (int j = 0; j < segments.length; ++j) { Segment 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前后是否一致,如果一致,說明期間沒有修改。
final V remove(Object key, int hash, Object value) { if (!tryLock()) scanAndLock(key, hash); V oldValue = null; try { HashEntry思考[] tab = table; int index = (tab.length - 1) & hash; HashEntry e = entryAt(tab, index); HashEntry pred = null; while (e != null) { K k; HashEntry 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); else pred.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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69569.html
摘要:作者鏈接來源簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。同時順手整理個人對源碼的相關理解,希望能夠稍微填補學習領域的空白。系列文章只會節選關鍵代碼輔以思路講解,請自行配合源碼閱讀。 作者:bromine鏈接:https://www.jianshu.com/p/2f6...來源:簡書著作權歸作者所有,本文已獲得作者授權轉載,并對原文進行了重新的排版。Swoft...
摘要:以下內容僅針對版書籍,等新版上市后,薦書欄目會對兩版的差異跟進介紹。當然,后續其它薦書的書目,也很有可能會送福利,一樣不容錯過。 showImg(https://segmentfault.com/img/bVbjIxq?w=6000&h=4000); 大家好,新一期的薦書欄目如期跟大家見面了。 先來看看今天的主角是誰:《Python源碼剖析——深度探索動態語言核心技術》,2008年出版...
摘要:在這種情況下,是以其為根的樹的最后一個結點。來源二總結底層是紅黑樹,能夠實現該集合有序如果在構造方法中傳遞了對象,那么就會以對象的方法進行比較。 前言 聲明,本文用得是jdk1.8 前面章節回顧: Collection總覽 List集合就這么簡單【源碼剖析】 Map集合、散列表、紅黑樹介紹 HashMap就是這么簡單【源碼剖析】 LinkedHashMap就這么簡單【源碼剖析】 本...
摘要:源碼剖析標準庫原文地址源碼剖析標準庫日志輸出構成日期空格時分秒空格內容源碼剖析互斥鎖,用于確保原子的寫入每行需寫入的日志前綴內容設置日志輔助信息時間文件名行號的寫入。 Golang 源碼剖析:log 標準庫 原文地址:Golang 源碼剖析:log 標準庫 日志 輸出 2018/09/28 20:03:08 EDDYCJY Blog... 構成 [日期][時分秒][內容] 源碼剖析 L...
閱讀 1370·2021-10-19 11:42
閱讀 717·2021-09-22 16:04
閱讀 1867·2021-09-10 11:23
閱讀 1838·2021-07-29 14:48
閱讀 1247·2021-07-26 23:38
閱讀 2812·2019-08-30 15:54
閱讀 1024·2019-08-30 11:25
閱讀 1694·2019-08-29 17:23