ConcurrentHashMap(jdk1.6)部分源码解析以及和HashMap的对比
首先看储存数据结构
HashMap:transient Entry[] table;
ConcurrentHashMap:final Segment<K,V>[] segments;
这里的每个Segment就相当于一个小的HashMap,而且因为是final修饰的,只能赋值一次,默认初始容量16。
static final class Segment<K,V> extends ReentrantLock implements Serializable {
看到Segment的类修饰继承等情况,发现它继承了ReentrantLock,由此可以想到,Segment是通过重入锁来保证线程安全。
到这里大致可以归纳出,ConcurrentHashMap通过重入锁保证线程安全,通过分段加锁减小锁粒度。
继续进入Segment
transient volatile HashEntry<K,V>[] table;
这里的和HashMap的Entry[] table差不多,通过volatile关键字保证可见性。也是通过数组加链表来实现HashMap。
来看最重要的put方法
public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false); }
这里和HashMap有个区别,HashMap是key不为空,这里是value不为空。
看segmentFor(hash)
final Segment<K,V> segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask]; }
hash定位返回一个Segment,继续看Segment的put
V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { int c = count; if (c++ > threshold) // ensure capacity rehash(); HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue; if (e != null) { oldValue = e.value; if (!onlyIfAbsent) e.value = value; } else { oldValue = null; ++modCount; tab[index] = new HashEntry<K,V>(key, hash, first, value); count = c; // write-volatile } return oldValue; } finally { unlock(); } }
因为继承了重入锁,lock() unlock()没什么好说的,里面的代码也很简单易懂,和jdk1.8之前的HashMap相似,定位数组,在链表中找是否存在,存在改掉原来的value,不存在新建一个。当count大于threshold时rehash()和HashMap的resize对应。
比较有意思的是两个需要对整个Map进行操作的函数containsValue和size。
看看size。
public int size() { final Segment<K,V>[] segments = this.segments; long sum = 0; long check = 0; int[] mc = new int[segments.length]; // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { check = 0; sum = 0; int mcsum = 0; for (int i = 0; i < segments.length; ++i) { sum += segments[i].count; mcsum += mc[i] = segments[i].modCount; } if (mcsum != 0) { for (int i = 0; i < segments.length; ++i) { check += segments[i].count; if (mc[i] != segments[i].modCount) { check = -1; // force retry break; } } } if (check == sum) break; } if (check != sum) { // Resort to locking all segments sum = 0; for (int i = 0; i < segments.length; ++i) segments[i].lock(); for (int i = 0; i < segments.length; ++i) sum += segments[i].count; for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } if (sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int)sum; }
因为size的同时可能伴随着修改,如put,remove等,所以它有一个检验机制,连续两次计算sum和check看有没有修改,如果两次都相等的话说明现在的值就是真正的size,如果检验失败,进入最麻烦的一步,把所有Segment上锁,计算size,再解锁。
在jdk1.8之后,对ConcurrentHashMap做了很大的改进,这个就以后再说了。