參考java併發編程的藝術一書中,對ConcurrentHashMap的講解
ConcurrentHashMap使用的是分段鎖Segment來保證不同的Segment區域互相不干擾,不存在鎖競爭關係,從而提升map的效率.
由於ConcurrentHashMap中存放的是Segment數組,每個Segment持有一個鎖,和HashEntry數組.
定位一個key應該在哪個segment中非常重要,如果大多數的key被定位到一個segment中,則這個機制的意義就不大了.因此要避免不同的hashcode被分配到同一個segment中去.
segment掩碼最終由於計算key在segment數組中的位置,他的值爲
segmentMask:segment數組長度-1
以put方法舉例(jdk版本1.7)
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); }
第6行中j即是定位segments位置的代碼.默認情況下segmentShift的值爲28,之所以無符號右移了28位,是因爲hash(key)中已經進行了取key.hashcode,多次左右移動
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.hashcode與掩碼mask(默認15)進行與有什麼後果呢
以下四個hashcode & 15的結果 (15的二進制位1111)
0001111 & 15 =15 0011111 & 15 =15 0111111 & 15 =15 1111111 & 15 =15
這樣就造成了只要低4位相同,則無論高位是否相同,最終結果都一樣,這樣的就造成了大量key被分配到同一個segment中.
採用rehash值算法後,j的值爲4,15,7,8就都不相同了
HashMap
由此推算HsahMap其實也做了小量reHash操作
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;
第6行中,其實hash(key)也做了簡單的rehash,避免大量key,分配到某一個Entry中
final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
總結:
ConcurrentHashMap和HashMap歸根結底,裏面都有一個數組,來存放Entry<K,V>,數組的大小是有限的.
一個key被映射到數組的哪個位置其實不重要,重要的是避免大量key映射到同一個位置.由於ConcurrentHashMap裏面位運算太多,以HashMap舉例,它拿到一個hash後,定位數組位置的算法是:
/** * Returns index for hash code h. */ static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
以HashMap默認的length:16舉例, h&15就是最終的位置.h是int型,有32位,而15只有低4位不爲0,則在按位與的場景下,只要低4位相同,則總會獲取相同的位置下標.rehash就是爲了消除這種較高衝突的可能,根據某種算法,打亂低4位,最終等到不同的位置下標.當然,如果兩個h一樣,是肯定會分配到相同的位置下標的