【问题】HashMap的computeIfAbsent方法丢失数据问题分析

news/2025/3/9 23:56:08/文章来源:https://www.cnblogs.com/cmxb/p/18761684

问题背景

前段时间碰到客户问题发现是 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());}
}

执行结果

image

查看直接结果会发现 循环添加的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相同的会导致数据丢失
在执行VisibilityDemomain方法时,

  • 执行第一个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执行时 firsti的值
    image

  • 第二个computeIfAbsent执行时 firsti的值
    image

  • 第一个computeIfAbsent开始赋值时,first和i的值,以及 tab[i]的值
    image

解决方法

  1. 升级JDK版本,目前测试jdk17执行相同代码会提示 ConcurrentModificationException异常,直接中止
  2. 禁止在 computeIfAbsent 方法中套用 该对象的computeIfAbsent方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/896167.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CFA学习

定量分析 利率 利率的定义:被认为是 ① 平衡借贷双方的平衡点(equilibrium interest rates)② 贴现率(货币的时间价值)③ 机会成本 利率的组成:通货膨胀➕各种风险 计算【现值/终值】用时间轴确实一目了然! # 经济学 # 财务报表分析 # 公司理财 # 投资组合管理 # 权益投…

INFINI Labs 产品更新 | Easysearch 增加异步搜索等新特性

INFINI Labs 产品更新发布!此次更新,Easysearch 增加了新的功能和数据类型,包括 wildcard 数据类型、Point in time 搜索 API、异步搜索 API、数值和日期字段的 doc-values 搜索支持,Console 新增了日志查询功能。 INFINI Easysearch v1.11.0 INFINI Easysearch 是一个分布…

语法trick

for (int i = 1; i <= n; ++i) {cout << dist[i] << " \n"[i == n];}

百万架构师第四十七课:并发编程的原理(二)|JavaGuide

原文链接 JavaGuide《并发编程的艺术》 并发编程的实现原理 目标上节课内容回顾 synchronized 原理分析 wait 和 notify Lock 同步锁回顾原子性 可见性 有序性JMM ​ JMM 是 JAVA 里边定义的内存模型。定义了多线程和我们内存交互的规范。屏蔽了硬件和操作系统访问内存的差异。…

[极客大挑战 2019]Havefun 1

进网站显示一只猫,于是看源代码 发现下面有注释<!--$cat=$_GET[cat];echo $cat;if($cat==dog){echo Syc{cat_cat_cat_cat};}-->所以在后面加上/index.php?cat=dog即可(小猫可爱捏)

提取excel中的图片

需求: 提取excel中嵌入单元格的图片 实现思路: 用pandas读取文件,对于嵌入图片的单元格则会显示其函数 问题:wps与office嵌入方法有所不同,wps使用函数嵌套,而office则是设置随单元格大小变动,对于后者,会被视为是悬浮的图片,使用pandas无法提取任何一张图片 源文件如…

2019年-PTA模拟赛-L2-1 链表去重(一维数组模拟链表)

一维数组模拟链表一维数组模拟链表 被删除的结点仍然在e数组中,因此记录一下被删除的结点的地址就可以找到其值AcCode: #include<bits/stdc++.h> using namespace std; int e[100010], ne[100010], vis[100010]; vector<int> delNode; int main(){int N, rootAdre…

C语言实验一作业

实验任务1:#include <stdio.h> int main() {printf(" O \n");printf("<H>\n");printf("I I\n");printf(" O \n");printf("<H>\n");printf("I I\n");return 0; }#include <stdio.h> in…

二、搭建MyBatis采用xml方式,验证CRUD(增删改查操作)

二、搭建MyBatis采用xml方式,验证CRUD(增删改查操作)@目录二、搭建MyBatis采用xml方式,验证CRUD(增删改查操作)2.1 开发环境2.2 创建maven工程2.3 创建User实体2.4 创建MyBatis的核心配置文件2.5 创建mapper接口2.6 创建MyBatis的映射文件2.7配置日志打印2.8 通过junit测…

MongoDB 的开源替代方案FerretD发了 2.0 版

FerretDB 宣布推出 2.0 版本,由 DocumentDB 驱动,作为 MongoDB 的开源替代方案。它带来性能提升、功能兼容性、向量搜索能力和复制支持。FerretDB 基于 Apache 2.0 许可发布,与 MongoDB 的驱动程序和工具兼容,可作为 MongoDB 5.0 及以上版本的直接替代品。FerretDB 2.x 利用…

Project(2)--WBS任务

创建工作分解结构WBS 创建一个初始的项目计划分成三步,依次是创建工作分解结构 设置任务之间的依赖关系 估算工期 选择“自动计划”设置所有新项目 “文件”-》“选项”-》“日程”-》显示层级关系“格式”-》显示大纲数字,查看任务之间的层级关系修改层级关系 选择“任务”-…