concurrenthashmap 的 putifabsent 是线程安全的原子操作,通过 cas 和细粒度锁保证“检查是否存在+不存在则插入”不可分割;它不支持延迟构造 value,高开销场景应选用 computeifabsent。

ConcurrentHashMap 的 putIfAbsent 本身就是线程安全的原子操作,无需额外同步。它底层通过 CAS(Compare-And-Swap)和锁分段(JDK 8+ 改为 synchronized + CAS 的细粒度桶锁)实现,能保证“判断是否存在 + 不存在则插入”这一整套逻辑的原子性。
putIfAbsent 的原子性到底指什么
它不是简单地“先 get 再 put”,而是单次调用内完成:如果 key 不存在,则插入并返回 null;如果 key 已存在,则不修改值,直接返回当前 value。整个过程不可分割,多个线程并发调用不会出现竞态条件(比如两个线程同时发现 key 不存在而都写入)。
例如:
- 线程 A 调用
map.putIfAbsent("id", new User("A"))→ key 不存在,插入成功,返回 null - 线程 B 几乎同时调用相同操作 → key 已被 A 写入,B 不覆盖,返回 A 写入的 User 对象
常见误用:在 putIfAbsent 外加 if 判断
下面写法是错误的,破坏了原子性:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
if (!map.containsKey("id")) { // 非原子:可能多个线程同时通过
map.put("id", new User("X")); // 非原子:可能重复写入
}
这等价于两次独立操作,中间存在时间窗口,导致重复插入或覆盖。应直接用:
User user = map.putIfAbsent("id", new User("X"));
if (user == null) {
// 确实是新插入的
} else {
// 原有值,user 就是已存在的对象
}
注意 value 计算的线程安全性
putIfAbsent 只保证“检查 + 插入”原子,不保护 value 构造过程。如果 value 创建开销大或有副作用,需自行控制:
- ✅ 推荐:value 是无状态、轻量对象(如 new String("a")、Integer.valueOf(1))
- ⚠️ 避免:value 构造中访问共享变量、调用外部服务、修改静态状态
- ? 若必须延迟创建,可用 computeIfAbsent(它只在真正需要插入时才执行 mappingFunction)
与 computeIfAbsent 的区别
当 value 构造成本高或依赖其他数据时,优先选 computeIfAbsent:
map.computeIfAbsent("id", key -> loadUserFromDB(key)); // 只在 key 不存在时调用 lambda
它同样原子,且 lambda 不会在 key 存在时执行,避免无效计算;而 putIfAbsent 总是先构造 value,再决定是否丢弃。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










