putIfAbsent在ConcurrentHashMap中线程安全,在HashMap中不安全;它原子性插入值,但仅适用于“不存在则设”,不适用于条件更新或复杂状态变更。

putIfAbsent 在并发场景下真的线程安全吗
putIfAbsent 本身是线程安全的——前提是用的是 ConcurrentHashMap,不是 HashMap。直接在普通 HashMap 上调用 putIfAbsent 不仅不解决并发覆盖,还会在多线程下抛 ConcurrentModificationException 或静默出错。
根本原因:普通 HashMap 的 putIfAbsent 只是语法糖(底层仍是 get + put 两步),没有原子性;而 ConcurrentHashMap.putIfAbsent 是 CAS 实现的原子操作。
- ✅ 正确做法:用
ConcurrentHashMap替代HashMap - ❌ 错误写法:
new HashMap().putIfAbsent(k, v)—— 无并发保护 - ⚠️ 注意:即使用了
ConcurrentHashMap,如果后续逻辑依赖“刚 put 成功就立刻读取”,仍可能因可见性或重排序出问题(需配合volatile或显式同步)
putIfAbsent 和 computeIfAbsent 的关键区别在哪
两者都原子地插入值,但触发时机和适用场景不同:
-
putIfAbsent(k, v):直接插入v(无论v是字面量、变量还是表达式结果),v 总是会被计算/构造一次,哪怕 key 已存在 -
computeIfAbsent(k, mappingFunction):只在 key 不存在时才调用mappingFunction,适合构造开销大、或有副作用的对象(比如 new 一个连接池、解析一段 JSON)
示例对比:
ConcurrentHashMap<String, List<String>> map = new ConcurrentHashMap<>();
// ❌ 每次都 new ArrayList(),即使 key 已存在
map.putIfAbsent("users", new ArrayList<>());
// ✅ 只在 key 不存在时才 new
map.computeIfAbsent("users", k -> new ArrayList<>());
为什么 putIfAbsent 有时看起来“没生效”
常见假象:调用 putIfAbsent 后,key 对应的 value 还是旧值,以为方法失效了。实际多为以下原因:
- 传入的
v是null:ConcurrentHashMap不允许nullvalue,会直接抛NullPointerException(注意不是静默失败) - key 的
equals/hashCode实现有问题:比如自定义对象作为 key,但没重写这两个方法,导致“看似相同”的 key 被当成不同 key 处理 - 误判返回值:该方法返回的是 插入前已存在的值(如果 key 已存在),或
null(如果插入成功)。很多人忽略返回值,只看 map 状态
建议检查方式:
String prev = map.putIfAbsent("id123", "new-value");
if (prev == null) {
// 插入成功
} else {
// prev 就是原来的值,没覆盖
}
替代方案:什么时候不该硬套 putIfAbsent
当业务逻辑需要“查-改-写”三步原子性(比如计数器自增、状态机流转),putIfAbsent 就不够用了:
- 它只保证“不存在则设”,不保证“存在则更新”
- 若需条件更新(如“只有当前值是 A 才设成 B”),该用
replace(k, oldV, newV)或compute系列 - 高频写场景下,过度依赖
putIfAbsent可能引发 CAS 自旋失败,吞吐下降(尤其竞争激烈时)
简单状态初始化可用 putIfAbsent,复杂状态变更请直奔 compute 或 merge。
最常被忽略的一点:putIfAbsent 的“absent”判断基于 key 是否存在,而非 value 是否为 null 或默认值。别拿它当“非空才设”的工具用。

















