HashMap 与 ConcurrentHashMap 有什么区别?
未标记
考查重点:并发访问与复合操作原子性。HashMap 不保证并发安全,ConcurrentHashMap 支持并发操作;复合读改仍用 compute 等原子接口,迭代语义也不同
本题目录
参考答案
核心回答
HashMap 是普通的哈希映射,适合线程内使用,或由外部统一保证同步的场景;ConcurrentHashMap 是为共享并发访问设计的映射,提供线程安全的查询、更新及部分原子复合操作。下面以 Java 25 的行为为准。
主要区别有四点:
- 并发保证不同。
HashMap自身不做同步,并发读写可能丢失更新、读取不一致;ConcurrentHashMap对容器操作提供相应的并发保证,普通查询一般不阻塞,可以与更新重叠进行。 - 实现方式不同。 两者都使用哈希桶,并可用链表或树处理冲突,但
ConcurrentHashMap还通过具有可见性保证的访问、CAS、桶级同步及协作扩容协调并发,而不是给所有操作加一把全局锁。 null约定不同。HashMap允许null键和null值;ConcurrentHashMap都不允许,使查询返回null可以明确表示没有映射。- 遍历语义不同。
HashMap的迭代器对违规结构修改采用尽力而为的fail-fast检测;ConcurrentHashMap的迭代器弱一致,允许遍历期间发生更新,不提供整张表的同一时刻快照。
多线程共享且需要频繁独立读写时,通常选 ConcurrentHashMap;线程内临时数据用 HashMap 更直接。但容器线程安全不等于整个业务操作自动原子。
ConcurrentHashMap 为什么能支持并发
普通 HashMap 的插入、链表修改、计数和扩容没有并发协调。以 OpenJDK 25 实现为例,ConcurrentHashMap 对空桶插入尝试 CAS,有冲突的普通更新在桶首节点上同步,并在获得锁后重新检查桶状态。不同桶的更新因此可以并行推进,同一桶内仍可能竞争。
节点字段及桶位访问的内存语义保证了必要的发布与可见性。某个键的更新,与读取到该更新值的查询之间存在 happens-before 关系;这不代表一次遍历或多次 get 能看到全表统一快照。
版本也要区分:JDK 7 的 ConcurrentHashMap 使用 Segment 分段锁;JDK 8 已改为桶数组、CAS 和桶级同步等机制,不能把 Segment 模型套到后续版本,也不能因为有 CAS 就称整个容器“无锁”。
为什么 get 后再 put 仍可能出错
假设两个线程都读取某商品浏览数为 10,各自加一后写回 11。每次 get、put 都安全,最终仍只增加了一次,因为“读、计算、写”不是一个原子操作。可以根据语义使用 merge、compute 或专门的并发计数器;“不存在才创建”可以使用 putIfAbsent、computeIfAbsent。
这类原子操作也有范围。computeIfAbsent 的计算函数应短小,不能在其中递归修改这张表;长时间远程调用可能阻塞其他相关更新。某个键被删除、计算返回 null 或失败后,后续仍可能重新计算,不能把它当成业务动作终身只执行一次的保证。
如果值是可变的 ArrayList,把它放进 ConcurrentHashMap 并不会让列表自身变成线程安全;拿到列表后并发修改仍需另行协调。转账等跨多个键的不变量也需要更大范围的同步或事务设计。
追问:能不能给 HashMap 加锁
可以,例如使用 Collections.synchronizedMap,或者让所有相关读写遵循同一把外部锁;遍历同步包装后的 Map 也要按其约定持锁。只锁写入、查询却绕过同步,不能普遍保证安全。初始化后不再修改且安全发布的只读 HashMap,则可以供多个线程读取。
如果需要在同一把锁下原子检查多项状态,统一加锁有其价值;如果大量操作只涉及独立键,ConcurrentHashMap 通常提供更细的并发粒度。两者的选择取决于业务原子性和竞争形态,不能用固定性能倍数判断。
参考资料
- 国内:JavaGuide:Java 集合常见面试题(下):用于比较并发控制、null 与复合操作,解释单次容器操作安全为何不等于业务步骤原子。
- 国内:JavaGuide:ConcurrentHashMap 源码分析:借鉴空桶 CAS、桶内同步和读取路径的说明,解释正文中的 JDK 8 并发协调,并区分 JDK 7 的 Segment 实现。
- 国内:美团技术团队:Java 8 系列之重新认识 HashMap:用于说明普通 HashMap 的桶结构、结构修改和并发使用风险,历史环链例子限定在文中 JDK 7 背景。
- 国外:Oracle:HashMap,Java SE 25:用于核对外部同步要求、null 支持及 fail-fast 仅用于诊断的边界。
- 国外:Oracle:ConcurrentHashMap,Java SE 25:用于核对读取的可见性、弱一致遍历、聚合状态限制,以及 computeIfAbsent 的原子范围与计算函数要求。
相关面经
7 篇曹操出行
后端实习concurrentHashMap比HashMap优点,解决了什么问题,底层怎么实现的
去哪儿
AI 应用开发hashmap是线程安全的吗,concurrenthashmap在jdk8中是如何实现线程安全的?
百度
后端开发工程师HashMap底层实现原理是什么?数据结构是怎么样的?是否线程安全?不使用ConcurrentHashMap、HashTable这种已有实现,你会如何改进现有的HashMap实现线程安全?
去哪儿
测试开发工程师HashMap和ConcurrentHashMap的原理与区别?
招银科技
后端开发工程师(AI / Agent 方向)Hashmap concurrenthashmap区别
