ConcurrentHashMap.computeIfAbsent 能保证线程安全,因为它基于 CAS 与细粒度 synchronized 实现原子性:key 不存在时仅一个线程执行 mappingFunction,其余线程等待并共享结果。

ConcurrentHashMap.computeIfAbsent 是 Java 并发编程中实现线程安全、按需初始化对象的高效方式,它天然避免了重复创建和锁竞争,比手动加锁或双重检查更简洁可靠。
为什么 computeIfAbsent 能保证线程安全
该方法在内部由 ConcurrentHashMap 自身的分段锁(JDK 8+ 是 CAS + synchronized 细粒度锁)保障:当 key 不存在时,它会原子性地执行你传入的 mappingFunction,并将结果插入 map;若多个线程同时调用且 key 未存在,**只有一个线程执行函数,其余线程阻塞等待并直接获取该次计算结果**,不会重复执行初始化逻辑。
典型用法:缓存单例对象或昂贵资源
比如为每个用户 ID 初始化一个线程安全的本地缓存(如 LoadingCache 的轻量替代):
ConcurrentHashMap<String, ConcurrentMap<String, Object>> userCaches = new ConcurrentHashMap<>();
// 线程安全地为 userId 获取专属缓存
ConcurrentMap<String, Object> cache = userCaches.computeIfAbsent(userId, id ->
new ConcurrentHashMap<>()
);
说明:
立即学习“Java免费学习笔记(深入)”;
- userId 作为 key,value 是一个独立的 ConcurrentHashMap,隔离不同用户的缓存空间
- lambda 中的 new ConcurrentHashMap() 只会被执行一次,即使 100 个线程同时请求同一 userId
- 返回值就是那个已初始化好的缓存实例,可直接 put/get,无需额外同步
注意事项和常见陷阱
虽然 computeIfAbsent 安全,但使用不当仍会出问题:
- mappingFunction 内不能依赖外部可变状态,否则可能因重试或并发导致不一致(例如读取了正在被其他线程修改的共享变量)
- 禁止在 mappingFunction 中调用当前 map 的其他写操作方法(如 put、remove),可能引发死锁(JDK 8 已修复部分场景,但仍建议规避)
- 如果初始化过程可能抛异常,异常会透出,key 不会写入 map;多次调用会反复触发函数,需自行捕获并兜底(例如返回空对象或预设默认值)
- 不适用于需要“先查再算再放”的复杂逻辑——它只适合「key 不存在 → 算一次 → 放进去」这种原子场景
对比手动同步方案,优势在哪
不用 computeIfAbsent 时,常见写法是:
synchronized (userCaches) {
ConcurrentMap<String, Object> cache = userCaches.get(userId);
if (cache == null) {
cache = new ConcurrentHashMap<>();
userCaches.put(userId, cache);
}
}
问题明显:
- 整个 map 被锁住,所有 userId 的初始化都串行,性能差
- 代码冗长,易漏掉同步块或判空
- 无法保证“只初始化一次”语义(极端情况下可能因指令重排等产生中间态)
而 computeIfAbsent 按 key 锁定,不同 key 完全并发,既安全又高效。



















