【问题】HashMap的computeIfAbsent方法丢失数据问题分析
问题背景
前段时间碰到客户问题发现是 ConcurrentHashMap的computeIfAbsent导致死循环(ConcurrentHashMap死循环问题分析)就很好奇HashMap的computeIfAbsent会不会也有问题,一试之下发现确实存在问题,相同的代码在HashMap中会丢失插入的数据。
发生原因
【循环添加】时,如果key的hash相同,会导致前面的值得覆盖,而不是追加,所以导致数据丢失
源码分析
可复现代码
public class VisibilityDemo {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
//循环添加
map.computeIfAbsent("AaAa",v->map.computeIfAbsent("BBBB",v1->"a"));
System.out.println("value:"+map+"size:"+map.size());
Map<String,String> map1 = new HashMap<>();
//分开添加
map1.computeIfAbsent("AaAa",v->"a");
map1.computeIfAbsent("BBBB",v1->"a");
System.out.println("value:"+map1+"size:"+map1.size());
}
}
执行结果

查看直接结果会发现 循环添加的map打印出来的value是一个,但是size=2,证明数据添加是执行了,但是数据丢失了
JDK源码 (1.8)
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
//1、如果table为null,进行初始化
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
//2、第一个节点不为null,进行赋值
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
V oldValue;
if (old != null && (oldValue = old.value) != null) {
afterNodeAccess(old);
return oldValue;
}
}
//3、执行 value的lambda表达式
V v = mappingFunction.apply(key);
if (v == null) {
return null;
} else if (old != null) {
old.value = v;
afterNodeAccess(old);
return v;
}
else if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
//4、给 tab 的第i个桶进行赋值
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return v;
}
通过查看【循环添加】和【分开添加】map打印出来的内容可以看出,在HashMap中循环添加hash相同的会导致数据丢失
在执行VisibilityDemo的main方法时,
-
执行第一个
computeIfAbsent,执行注释中的 1 2 3 个步骤,步骤3时开始执行第二个computeIfAbsent方法; -
此时代码逻辑认为是在构造first节点,所以此时first=null;
-
第二个
computeIfAbsent方法执行2 3 4,给tab对象的第i个桶设置first节点; -
此时 i=15,所以是给tab的第15个桶进行赋值,然后返回,继续执行第一个
computeIfAbsent; -
第一个
computeIfAbsent还会执行一次步骤4,因为hash相同所以 同样是给 i=15的桶赋值,导致第二个computeIfAbsent的值丢失了 -
第一个
computeIfAbsent执行时first和i的值
![image]()
-
第二个
computeIfAbsent执行时first和i的值
![image]()
-
第一个
computeIfAbsent开始赋值时,first和i的值,以及 tab[i]的值
。
解决方法
- 升级JDK版本,目前测试jdk17执行相同代码会提示
ConcurrentModificationException异常,直接中止 - 禁止在 computeIfAbsent 方法中套用 该对象的computeIfAbsent方法。



。
浙公网安备 33010602011771号