HashMap :先說HashMap,HashMap是線程不安全的,在併發環境下,可能會形成環狀鏈表(擴容時可能造成,具體原因自行百度google或查看源碼分析),導致get操作時,cpu空轉,所以,在併發環境中使用HashMap是非常危險的。
HashTable : HashTable和HashMap的實現原理幾乎一樣,差別無非是1.HashTable不允許key和value爲null;2.HashTable是線程安全的。但是HashTable線程安全的策略實現代價卻太大了,簡單粗暴,get/put所有相關操作都是synchronized的,這相當於給整個哈希表加了一把大鎖,多線程訪問時候,只要有一個線程訪問或操作該對象,那其他線程只能阻塞,相當於將所有的操作串行化,在競爭激烈的併發場景中性能就會非常差。
HashTable性能差主要是由於所有操作需要競爭同一把鎖,而如果容器中有多把鎖,每一把鎖鎖一段數據,這樣在多線程訪問時不同段的數據時,就不會存在鎖競爭了,這樣便可以有效地提高併發效率。這就是ConcurrentHashMap所採用的"分段鎖"思想。
ConcurrentHashMap採用了非常精妙的"分段鎖"策略,ConcurrentHashMap的主幹是個Segment數組。
Segment類似於HashMap,一個Segment維護着一個HashEntry數組
成員變量在看HashMap的源碼的時候基本上已經講解過了
基本上所有的構造方法最終會調用當前方法
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); //concurrencyLevel 併發數 if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // int sshift = 0; int ssize = 1; //size爲Segment大小 while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } //segmentShift和segmentMask這兩個變量在定位segment時會用到 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; //2 while (cap < c) cap <<= 1; //創建 segments cap*loadFactor 擴容閥值 cap HashEntry大小 Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); //創建segments數組 Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); this.segments = ss; } |
put方法講解(UNSAFE自己可以去看一下)
public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); //根據key映射 int hash = hash(key); //定位segment int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment //找到對應的segment s = ensureSegment(j); //調用對應的segment的put方法 return s.put(key, hash, value, false); } //找到對應的segment 如果沒有則創建一個 private Segment<K,V> ensureSegment(int k) { final Segment<K,V>[] ss = this.segments; long u = (k << SSHIFT) + SBASE; // raw offset Segment<K,V> seg; if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { Segment<K,V> proto = ss[0]; // use segment 0 as prototype int cap = proto.table.length; float lf = proto.loadFactor; int threshold = (int)(cap * lf); HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap]; if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck Segment<K,V> s = new Segment<K,V>(lf, threshold, tab); while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) break; } } } return seg; } 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; //找到對應的節點 如果有值 則更新節點 修改節點指向 沒有的話 創建HashEntry 放入我們的值 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; } |
get方法 (不需要加鎖)
public V get(Object key) { Segment<K,V> s; // manually integrate access methods to reduce overhead HashEntry<K,V>[] tab; //計算hash值 int h = hash(key); //找到對應segment索引值 找到對應的HashEntity 去除值 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; } |
總結看懂了put方法 其他的方法都很easy 自己看 本文不做講解
ConcurrentHashmap主要使用Segment來實現減小鎖粒度,把HashMap分割成若干個Segment,在put的時候需要鎖住Segment,get時候不加鎖,使用volatile來保證可見性,當要統計全局時(比如size),首先會嘗試多次計算modcount來確定,這幾次嘗試中,是否有其他線程進行了修改操作,如果沒有,則直接返回size。如果有,則需要依次鎖住所有的Segment來計算。