computeifabsent通过cas+细粒度synchronized锁实现原子性:先用cas插入reserved占位节点,再执行mappingfunction,最后写入实际值并唤醒等待线程;普通if+put因竞态窗口会导致重复计算与覆盖。

computeIfAbsent 通过底层 CAS + 细粒度 synchronized 锁机制,把“查不存在”和“计算并写入”合并成一个不可分割的操作,确保对同一 key 的初始化逻辑只执行一次。
原子性靠什么实现
它不是简单的“先 get 再 put”,而是直接在对应 hash 桶(bin)上完成判断与插入:
- JDK 8+ 使用 CAS 尝试插入 RESERVED 节点作为占位,标识该 key 正在被初始化
- 若 CAS 成功,当前线程进入 mappingFunction 执行构造逻辑;其他线程发现 RESERVED 状态后会阻塞等待
- 构造完成后,用 final 字段写入实际值,并唤醒等待线程——所有线程最终拿到的是同一个结果
- 整个过程不依赖全局锁,只锁定目标桶位,吞吐高、竞争小
为什么普通 if + put 不行
典型错误写法会打开竞态窗口:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
if (!map.containsKey(k)) map.put(k, create());—— 两个线程可能同时通过判断,都执行 create() - 结果是重复构造、部分值被覆盖、资源浪费(如多次 DB 查询、HTTP 请求)
- 而 computeIfAbsent 把这个判断和写入压进一个原子步骤,天然规避该问题
使用时的关键约束
它的原子性有前提,违反就可能失效或出错:
- mappingFunction 必须无副作用:不能发请求、改 DB、写日志——异常重试或并发触发会导致意外交互
- 不能递归调用 computeIfAbsent 或 put 等写操作:内部持有桶锁,容易死锁
- 函数返回不能为 null:否则本次调用失败,key 不写入,下次调用还会再试
- key 的 equals/hashCode 必须正确:否则找不到已有 entry,反复初始化
它返回什么、什么时候执行
行为明确,便于预期:
- 只要 key 对应的 value 非 null(哪怕之前是 put 进去的),就跳过 mappingFunction,直接返回现有值
- 仅当 key 不存在 或 value == null 时,才执行 mappingFunction
- 返回值总是最终存入 map 的那个值——不管是不是这次新创建的
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










