ConcurrentHashMap用了悲观锁还是乐观锁?
它不是单一锁策略:读取尽量无锁,更新结合 CAS 与桶级同步,判断应落到具体操作和 JDK 版本。
直接结论
不能简单回答“悲观锁”或“乐观锁”。Java 21 的 ConcurrentHashMap 在不同路径组合使用无锁读取、CAS 原子更新以及桶级 synchronized 协作:空桶插入通常尝试 CAS,发生桶冲突时会对桶头加同步锁,扩容时多个线程还可协助迁移。它的目标是让读取具有高并发度,让更新尽量缩小互斥范围,而不是为整个 Map 使用一把全局锁。面试时应强调这是并发算法的组合,而非二选一标签。
原理与步骤
get 根据 volatile 可见的表和节点字段查找,不为普通读取获取全局互斥锁;putIfAbsent、compute 等复合操作必须保证原子语义,在无竞争路径先使用 CAS,在有竞争或链表、树结构修改时进入局部同步。size 相关统计也通过分散计数降低热点。官方 API 承诺线程安全、检索通常不阻塞以及更新具有 happens-before 可见性,但不会承诺内部一定使用某一种锁或某个字段,因此内部机制要注明所讨论的 JDK 版本。
工程场景
在缓存索引、连接注册表等读多写少场景,ConcurrentHashMap 能避免用 Collections.synchronizedMap 把所有访问串行化。对于“若不存在则加载”的逻辑,应使用 computeIfAbsent 等原子 API,而不是先 containsKey 再 put;映射函数要短小,避免递归更新同一映射,也不要在其中执行不可控的慢 I/O。热点单键频繁更新仍可能形成竞争,需要考虑 LongAdder、分片或业务模型调整,而不是误以为并发容器能消除所有锁等待。
验证与边界
可以用多线程压力测试验证原子操作结果和吞吐量,但不能用一次基准就断言所有负载都无锁。迭代器是弱一致的,不等同于数据库快照;size 在并发变化时更适合监控估计,不应作为跨多个操作的事务条件。空键和值不被允许。回答还要区分 API 的并发保证与当前 OpenJDK 实现细节:未来 JDK 可改变 CAS、锁或数据结构,只要仍满足公共契约,因此生产代码不应反射依赖内部节点。
答题练习
- 1读取通常无全局锁
- 2空桶更新可走 CAS
- 3冲突更新使用桶级同步
常见错误
- 把容器贴成纯乐观锁或纯悲观锁
- 用 containsKey 加 put 模拟原子操作
可能追问
- computeIfAbsent 有哪些使用约束
- 弱一致迭代器意味着什么
来源记录
- 原始来源
- 小林coding
- 来源页面
- Java集合面试题
- 最近收录
- 2026-07-04
- 官方复核
- Oracle Java 官方文档