ConcurrentHashMap 1.7
数据结构:每一个实际存放元素的HashEntry数组由不同的Segment锁保护,HashEntry是一个链表。
1.put
put时先定位到Segment上,
s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)
这里没有用volatile读是因为如果Segment已经初始化,后续不会对它的reference进行更改,如果没有初始化,会在ensureSegment()中进行volatile读校验。
然后对该Segment尝试进行加锁,失败后会自旋tryLock一定次数,如果还没获取到锁,调用lock阻塞,等待获取锁。
获取锁之后,接着put过程就和HashMap差不多了,定位到HashEntry数组的下标,然后使用链地址法放入元素。最后在finally中释放锁。
因为每次只锁住一个Segment,所以多个Segment的put可以并发执行。最多支持Segment数组长度的并发数。
2.get
在说get前首先提一点:Segment的table属性是volatile的,HashEntry的value和next属性是volatile的,对于一个volatile变量的写操作先行发生于之后对该volatile变量的读操作。
get操作不会获取锁。这可能会导致前一个线程修改了map对另一个线程不会立即可见。
首先定位到Segment,这里用volatile读Segment,保证并发read和write时Segment已经初始化但对其他线程不可见的问题。s.table是volatile的,另一个线程初始化了之后,此线程可见。
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
// 对volatile修饰的对象或数组而言,其含义是对象或数组的引用具有可见性,但是数组或对象内部的成员改变不具备可见性。
// 对于加锁的put和没有加锁的get之间并不存在happens-before 所以新增Entry时要volatile进行put 此处volatile进行get
// volatile的value和next保证了别的线程修改Entry后对此线程立即可见
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;
}
}
3.size
size分两步,
第一个步遍历所有的segment,累加modCount和count,如果前后两次遍历累加的modCount得到一样的值,就返回累加的count。
如果重试次数==RETRIES_BEFORE_LOCK(第三次循环满足该条件,也就是比较两次都不一致后),会锁住所有的segment,并进行上一步的计算。此循环最多执行5次,最少执行2次。