知道HashTable是通过synchronized将整个表锁定,线程独占。而ConcurrentHashMap内部使用Segment表示Hash表的不同部分,每个Segment相当于一个HashTable,HashEntry[]构成,对不同Segment的修改可以并发进行。但有些方法是需要锁定整张表的,比如size(),contiansValue()。
1,ConcurrntHashMap初始设置为:
默认初始容量为16,最大容量1<<30
默认负载因子为0.75,
concurrentHashMap由Segment<K,V>数组组成,默认初始数量为16,最大为1<<16
2,Segment继承自ReentrantLock:
数据成员如下:
static final class Segment<K,V> extends ReentrantLock implements Serializable { static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; transient volatile HashEntry<K,V>[] table; transient int count; transient int modCount; transient int threshold; final float loadFactor; Segment(float lf, int threshold, HashEntry<K,V>[] tab) { this.loadFactor = lf; this.threshold = threshold; this.table = tab; } }3,HashEntry数据成员:
static final class HashEntry<K,V> { final int hash; final K key; volatile V value; volatile HashEntry<K,V> next; }
ConcurrentHashMap的remove和put操作都是交给对应的Segment的方法去做:
删除操作:
public V remove(Object key) { int hash = hash(key); Segment<K,V> s = segmentForHash(hash); //定位对应的Segment return s == null ? null : s.remove(key, hash, null); //让找到的Segment去做 }先根据key定位Segment,然后委托给Segment的remove操作,只要remove在不同段操作,可并发进行。下面是Segment的remove方法:
/** * Remove; match on key only if value null, else match both. */ 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; //根据hash值找到HashEntry下标 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))) {//找到key对应的 V v = e.value; if (value == null || value == v || value.equals(v)) { //比较value是否相等 if (pred == null) setEntryAt(tab, index, next);//找到的是头结点,设置next为头结点 else pred.setNext(next); //删除找到的结点 ++modCount; --count; oldValue = v; } break; } pred = e; e = next; } } finally { unlock(); } return oldValue; }put方法:
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); }
找到对应的Segment,以下是Segment的put方法:
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; int index = (tab.length - 1) & hash; //定位到对应HashEntry的索引 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))) { key相等或者(hash相等,值相等) 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; }
上面说size()锁定全部表,以下是size()源码:
public int size() { // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. final Segment<K,V>[] 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) { //尝试几次后没有成功,锁定map for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); //所有的Segment被锁定 } 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) //加上每个Segment的元素数量count overflow = true; } } if (sum == last)//如果跟上一次的modeCount总数相同,说明求size中间map没有修改,结果有效 break; last = sum; } } finally { if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); //所有的Segment解锁 } } return overflow ? Integer.MAX_VALUE : size; }
先采用不加锁的方式,连续计算元素的个数,最多计算3次:
1、如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;2、如果前后两次计算结果都不同,则给每个Segment进行加锁,再计算一次元素的个数;