concurrenthashmap的putifabsent天然支持原子插入,基于cas与细粒度synchronized锁确保“检查是否存在”与“插入新值”不可分割;返回旧值(null表示插入成功),value为null会抛npe,适用于缓存初始化等场景。

ConcurrentHashMap 的 putIfAbsent 方法天然支持原子插入,无需额外同步,只要键不存在就插入,存在则返回已有值。
putIfAbsent 的原子性原理
该方法底层基于 CAS(Compare-and-Swap)和锁分段(JDK 8+ 改为 synchronized + CAS 细粒度加锁),保证“检查是否存在”和“插入新值”两个动作不可分割。整个操作对指定 key 是线程安全的,不会出现多个线程同时插入相同 key 导致覆盖或重复计算的问题。
基本用法:插入默认对象
适合缓存初始化、单例注册、防重复创建等场景。例如,只允许一个线程创建并缓存某个 expensiveObject:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
ConcurrentHashMap<string expensiveobject> cache = new ConcurrentHashMap();
ExpensiveObject obj = cache.putIfAbsent("key1", new ExpensiveObject());
// obj 为 null 表示本次插入成功;否则是其他线程已插入的实例
if (obj == null) {
obj = cache.get("key1"); // 确保拿到刚插入的实例(也可直接用上面返回值)
}
</string>配合 computeIfAbsent 实现延迟构造
如果构造值开销大,且希望仅在 key 不存在时才执行创建逻辑,推荐用 computeIfAbsent——它更安全(避免重复构造)且语义更清晰:
ExpensiveObject obj = cache.computeIfAbsent("key1", k -> new ExpensiveObject());
// lambda 只会在 key 不存在时执行一次,绝对不重复调用
注意:putIfAbsent 会**立即执行** new ExpensiveObject(),即使最终没插入成功(比如其他线程抢先插入),也会造成浪费。computeIfAbsent 才真正实现“按需构造”。
常见误用与注意事项
- 不要用 putIfAbsent 替代 getOrDefault + put 的组合来实现“读-改-写”,它不保证后续修改的原子性
- 返回值是 key 对应的当前值(可能是刚插入的,也可能是已存在的),不是布尔标识,需通过是否为 null 判断是否插入成功
- value 为 null 时会抛 NullPointerException,ConcurrentHashMap 不允许 null 值
- key 和 value 都必须非 null,否则运行时报 NPE
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










