computeifabsent 的原子性指对同一 key,mappingfunction 最多执行一次且其他线程阻塞等待;底层通过 cas 插入 reserved 节点与桶级 synchronized 协同实现,锁仅限于对应哈希桶。

computeIfAbsent 的原子性不是指整个“读–计算–写”全程绝对不可中断,而是特指:对同一个 key,mapping function 最多只被执行一次,且执行期间其他线程对该 key 的同次 computeIfAbsent 调用会阻塞等待结果——这个关键约束由 ConcurrentHashMap 内部的细粒度同步机制保障。
底层靠 CAS + 桶级锁协同控制
在 JDK 8 及以后,ConcurrentHashMap 不再使用分段锁(Segment),而是基于 Node 数组 + CAS + synchronized 实现更精细的并发控制:
- 当 key 对应的桶(bin)为空时,先尝试 CAS 插入一个特殊标记节点(RESERVED),表示“此 key 正在初始化中”;
- 若 CAS 成功,当前线程获得唯一执行 mappingFunction 的资格;
- 若 CAS 失败(说明已有线程抢先占位),当前线程就等待该桶上已有的 synchronized 锁释放,并直接读取最终写入的值;
- 整个过程锁作用范围仅限于该 key 所在的哈希桶,不影响其他 key 的并发操作。
它不保证什么,但明确禁止什么
理解它的边界比知道它能做什么更重要:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 不保证 mappingFunction 内部逻辑的线程安全——函数本身必须无副作用、不修改共享状态、不递归调用自身或同一 map 的写方法;
- 不保证返回 null 或抛异常后的状态一致性:JDK 8 中若 mappingFunction 抛异常,key 可能残留为 RESERVED 状态导致后续线程永久阻塞;JDK 9+ 改进为自动清理;
- 禁止在 mappingFunction 中再次调用 computeIfAbsent、merge、compute 等可能触发锁竞争的方法,否则极易死锁;
- 不承诺“查无则建”整体流程对外完全透明——比如 size() 或 containsKey() 的结果不能作为 computeIfAbsent 的前置判断依据,因为二者无同步关系。
为什么比 if-put 组合更安全
手动实现“先查后 put”天然存在竞态窗口:
- 线程 A 查 key 不存在 → 线程 B 同时查 key 也不存在 → 两者都执行 expensiveCreate() → 其中一个结果被 put 覆盖,造成重复计算和资源浪费;
- computeIfAbsent 把查与写绑定在同一把桶锁下,从根源上关闭这个窗口;
- 即使 mappingFunction 耗时较长,也只是让其他线程等待这一次结果,而非各自重复执行。
适合这样用,效果最稳
发挥它优势的关键是控制 mappingFunction 的行为边界:
- 返回确定、轻量的对象:如
Pattern.compile("xxx")、new SimpleDateFormat(...); - 做纯内存构造,避免 IO、数据库查询、远程调用等长耗时操作——真有这类需求,应在外层加异步/缓存策略,而非塞进 computeIfAbsent;
- 确保函数幂等且无外部依赖:不读写 static 变量、不操作其他共享容器、不改变传入参数以外的任何状态。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










