当前位置: 首页 > news >正文

ConcurrentHashMap 原子操作详解:computeIfAbsent、computeIfPresent和putIfAbsent

🧑 博主简介:CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程高并发设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea

在这里插入图片描述


在这里插入图片描述

文章目录

  • ConcurrentHashMap 原子操作详解:`computeIfAbsent`、`computeIfPresent` 和 `putIfAbsent`
    • 引言
    • 1. `computeIfAbsent` - 原子性缺失时创建
      • 方法定义
      • 核心功能介绍
      • 特性
      • 典型使用场景
      • 示例:用户会话管理
    • 2. `computeIfPresent` - 原子性存在时更新
      • 方法定义
      • 核心功能介绍
      • 特性
      • 典型使用场景
      • 示例:库存管理系统
    • 3. `putIfAbsent` - 原子性条件插入
      • 方法定义
      • 核心功能介绍
      • 特性
      • 典型使用场景
      • 示例:简单缓存系统
    • 方法对比总结
    • 最佳实践总结
      • 1. 优先选择 `computeIfAbsent`
      • 2. 复杂更新使用 `computeIfPresent`
      • 3. 组合使用模式
      • 4. 避免的陷阱
      • 5. 性能考虑

ConcurrentHashMap 原子操作详解:computeIfAbsentcomputeIfPresentputIfAbsent

引言

在多线程编程的战场上,ConcurrentHashMap 犹如一把精密的瑞士军刀,而它的核心方法 computeIfAbsentcomputeIfPresentputIfAbsent 则是刀锋上最锐利的三道刃光。在百万级并发的洪流中,这些方法承载着构建线程安全数据结构的重任,却常被开发者误用或低估。

当多个线程如潮水般涌向同一个键值对时,如何确保对象只创建一次?如何实现原子更新而不引发数据竞争?这正是这些方法存在的意义——它们通过桶级别锁和精心设计的原子语义,在保持高性能的同时解决了并发编程中最棘手的可见性与原子性问题。

本文将深入剖析这三个方法的实现机制、适用场景与实战技巧。无论是构建高性能缓存、实现分布式计数器,还是设计状态机转换逻辑,正确选择这些方法都能让您的代码在并发风暴中岿然不动。理解它们之间的微妙差异,正是从普通开发者晋升为并发编程艺术家的关键一步。

下面是对 ConcurrentHashMap 中三个关键方法的详细解析,包括功能说明、使用场景和典型示例:


1. computeIfAbsent - 原子性缺失时创建

方法定义

V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

核心功能介绍

  • key 不存在 或对应的值为 null
  • 执行提供的 mappingFunction 创建新值
  • 将新值插入 Map 并返回
  • 如果 key 已存在,直接返回现有值(函数不执行)

特性

  • ✅ 原子性操作(桶级别锁)
  • ⚠️ 映射函数不能返回 null(抛出 NPE)
  • ⚡ 映射函数应保持轻量(执行时会阻塞相同桶的操作)

典型使用场景

  1. 延迟初始化(按需创建对象)
  2. 实现线程安全的缓存
  3. 为每个 key 创建复杂数据结构
  4. 防止重复资源创建

示例:用户会话管理

ConcurrentHashMap<String, UserSession> sessionCache = new ConcurrentHashMap<>();public UserSession getSession(String userId) {return sessionCache.computeIfAbsent(userId, id -> {// 仅当用户会话不存在时创建(数据库查询等)UserSession session = new UserSession(id);session.loadPreferences(); // 加载用户偏好设置return session;});
}// 测试:多个线程获取同一用户的会话
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {pool.submit(() -> {UserSession session = getSession("user123");// 所有线程获得相同会话实例});
}

2. computeIfPresent - 原子性存在时更新

方法定义

V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

核心功能介绍

  • key 存在 且对应的值 非 null
  • 执行提供的 remappingFunction 计算新值
  • 如果函数返回非 null,更新键值对
  • 如果函数返回 null,删除该键值对
  • 如果 key 不存在,直接返回 null(函数不执行)

特性

  • ✅ 原子性更新操作
  • 🗑️ 可通过返回 null 删除条目
  • 🔄 适合状态转换和条件更新

典型使用场景

  1. 原子计数器更新
  2. 状态转换逻辑
  3. 条件删除条目
  4. 修改现有对象状态

示例:库存管理系统

ConcurrentHashMap<String, AtomicInteger> inventory = new ConcurrentHashMap<>();// 初始化库存
inventory.put("widget", new AtomicInteger(100));
inventory.put("gadget", new AtomicInteger(50));// 原子性减少库存
public boolean sellProduct(String productId, int quantity) {return inventory.computeIfPresent(productId, (id, stock) -> {int current = stock.get();if (current >= quantity) {stock.set(current - quantity);return stock; // 更新库存}return null; // 库存不足,删除条目(触发补货)}) != null; // 返回是否成功销售
}// 使用示例
sellProduct("widget", 3);  // 成功,库存变为97
sellProduct("gadget", 60); // 失败,库存不足

3. putIfAbsent - 原子性条件插入

方法定义

V putIfAbsent(K key, V value)

核心功能介绍

  • key 不存在
  • 插入提供的 value
  • 返回先前与 key 关联的值(null 表示插入成功)
  • 如果 key 已存在,不执行任何操作

特性

  • ✅ 原子性检查并插入
  • ⚠️ 可能创建多余对象(值在调用前已创建)
  • ⏱️ 比 computeIfAbsent 更轻量(无函数计算)

典型使用场景

  1. 对象创建成本低的场景
  2. 简单存在检查插入
  3. Java 7 兼容代码
  4. 需要明确知道是否插入的场景

示例:简单缓存系统

ConcurrentHashMap<String, Config> configCache = new ConcurrentHashMap<>();public Config getConfig(String configName) {Config config = new Config(configName); // 可能创建多余对象// 尝试放入缓存Config existing = configCache.putIfAbsent(configName, config);if (existing != null) {return existing; // 使用已存在的配置}return config; // 使用新创建的配置
}// 优化版:结合 computeIfAbsent 避免多余创建
public Config getConfigOptimized(String configName) {return configCache.computeIfAbsent(configName, Config::new);
}

方法对比总结

特性computeIfAbsentcomputeIfPresentputIfAbsent
触发条件Key 不存在Key 存在且值非 nullKey 不存在
主要功能创建 + 插入更新 + 删除条件插入
返回值新值或现有值新值或 null(删除时)先前值或 null
对象创建控制✅ 按需创建(推荐)❌ 不适用⚠️ 可能创建多余对象
更新能力❌ 只能创建✅ 可更新/删除❌ 只能插入
删除能力❌ 不能删除✅ 可删除(返回 null)❌ 不能删除
函数参数Function<K, V>BiFunction<K, V, V>预创建的值
适用场景延迟初始化/缓存状态转换/原子更新简单条件插入
Java 版本8+8+5+

最佳实践总结

1. 优先选择 computeIfAbsent

// ✅ 推荐 - 避免多余对象创建
map.computeIfAbsent(key, k -> createExpensiveObject());// ⚠️ 不推荐 - 可能创建多余对象
Value obj = new Value();
map.putIfAbsent(key, obj);

2. 复杂更新使用 computeIfPresent

// 原子性状态机更新
map.computeIfPresent(key, (k, v) -> {if (v.canTransition()) {return v.nextState();}return v; // 保持原状态
});

3. 组合使用模式

ConcurrentHashMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();public boolean allowRequest(String api) {// 确保限流器存在RateLimiter limiter = limiters.computeIfAbsent(api, this::createRateLimiter);// 原子性更新使用计数limiters.computeIfPresent(api, (k, v) -> {v.recordRequest();return v;});return limiter.tryAcquire();
}

4. 避免的陷阱

// 错误1:在函数内操作当前Map(可能导致死锁)
map.computeIfAbsent(keyA, k -> {return map.computeIfAbsent(keyB, k2 -> "value"); // ⚠️ 递归调用
});// 错误2:忽略空值处理
map.computeIfPresent(key, (k, v) -> {if (shouldRemove(v)) return null; // ✅ 正确删除return v;
});

5. 性能考虑

// 轻量操作:使用 putIfAbsent
map.putIfAbsent(key, simpleValue);// 重量操作:使用 computeIfAbsent(避免重复创建)
map.computeIfAbsent(key, k -> {return database.loadResource(k); // 耗时操作
});

以上就是针对ConcurrentHashMap 核心三个方法computeIfAbsent、computeIfPresent 和 putIfAbsent的详细介绍。

通过理解这三个核心方法的特性和适用场景,我们可以编写出更高效、线程安全的并发代码。在实际开发中:

  • 90% 的场景优先使用 computeIfAbsent
  • 需要更新逻辑时使用 computeIfPresent
  • 仅当对象创建成本低且需要兼容旧Java版本时使用 putIfAbsent
http://www.lryc.cn/news/588450.html

相关文章:

  • C语言-数据输入与输出
  • 《甘肃棒球》国家级运动健将标准·棒球1号位
  • c#进阶之数据结构(动态数组篇)----Queue
  • Javaweb使用websocket,请先连上demo好吧!很简单的!
  • Vim库函数
  • 【DOCKER】-4 dockerfile镜像管理
  • 纯C++11实现!零依赖贝叶斯情感分析系统,掌握机器学习系统工程化的秘密!
  • 学习 Flutter (三):玩安卓项目实战 - 上
  • 机器学习、深度学习、神经网络之间的关系
  • redis配置(Xshell连接centos7的基础上)
  • Mysql数据库学习--多表查询
  • Python中使用Re模块TypeError: cannot use a string pattern on a bytes-like object 解决办法
  • Leaflet面试题及答案(81-100)
  • 九、官方人格提示词汇总(中-1)
  • 项目进度图不直观,如何优化展示方式
  • Go泛型完全指南:从基础到实战应用
  • 进程---基础知识+命令+函数(fork+getpid+exit+wait+exec)
  • iOS —— 网易云仿写
  • 短剧看广告APP源码独立部署与二次开发指南(支持二开)
  • 前端vue对接海康摄像头流程
  • Java学习 -------进程、线程、协程
  • 无人机抗风性模块概述!
  • 修改主机名颜色脚本
  • Pytest Fixtures 详解:轻松掌握测试逻辑复用与资源管理
  • 如何删除D盘合并C盘
  • 搭建k8s高可用集群,“Unable to register node with API server“
  • JAVA并发——volatile关键字的作用是什么
  • 【EM算法】算法及注解
  • aspnetcore Mvc配置选项中的ModelBindingMessageProvider
  • 群晖Nas - Docker(ContainerManager)上安装SVN Server和库权限设置问题