java_ConcurrentHashMap源码解析
从源码角度分析ConcurrentHashMap是如何实现线程安全的
HashMap 多线程下的问题
HashMap限制了数组的Length为2的幂次,是为了实现计算Hash值所处数组位置时,能正确计算。
举例:如果设置length为5,那么实际计算的值为4,二进制为100。100与Hash值进行位|运算,结果只有0和4,不能散列到1,2,3三个数组位置上,因此length只能为2的幂次。
resize死循环
当HashMap数组size>Length*LoadFactor时,就会执行resize方法扩容HashMap。
- 创建一个新的,长度为原来Capacity两倍的数组,保证新的Capacity仍为2的N次方,从而保证上述寻址方式仍适用。
- 通过如下transfer方法将原来的所有数据全部重新插入(rehash)到新的数组中。
1 | void transfer(Entry[] newTable, boolean rehash) { |
这里如果同时有多个线程,来同时put数据,触发了resize方法,可能会导致数据链表产生死循环。
Fast-fail机制
父类声明了modCount成员变量,每当HashMap数据发生变化时,就会把modCount++。
在迭代获取数据,序列化数据等时候,都会获取modCount,在结束时再次获取modCount,进行比对。如果数据发生变化则抛出ConcurrentModificationException
。
解决方式
- 使用
Collections.synchronizedMap
方法构造出一个同步Map - 直接使用ConcurrentHashMap替代HashMap
Collection.synchronizedMap
1 | public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { |
SynchronizedMap内部封装了一个Map类型的成员变量,所有可能会产生并发问题的方法,都增加了同步代码块。
默认情况下,同步块对该Map对象加锁,类对象锁。
由此可知,这样的实现是比较粗糙的,因此不建议使用,我更推荐的是ConcurrentHashMap。
ConcurrentHashMap
1 | // 内部table 增加volatile标注 |
初始化
1 | private final Node<K,V>[] initTable() { |
put操作
1 | final V putVal(K key, V value, boolean onlyIfAbsent) { |