HashMap 在并发环境下可能出现哪些问题?

考查重点:并发写冲突与中间状态。无协调并发写会丢更新、计数不准或读到中间状态,遍历也可能异常;共享访问换并发容器或统一同步

本题目录

参考答案

核心回答

HashMap 自身不提供同步。如果多个线程无协调地并发读写,可能发生条目丢失、计数不准确、读取旧值或中间状态,以及遍历异常。根本原因是插入、链表修改、扩容和计数等步骤没有形成一个受保护的操作,也缺少必要的线程间可见性保证。

还要区分版本:JDK 7 的并发扩容存在头插迁移形成环链、导致查询无法结束的经典风险;JDK 8 改变了链表迁移方式,但 HashMap 仍不是线程安全容器,不能因为这个历史问题得到缓解就并发使用。

实际选择是让 Map 限于单线程、将构建好的只读 Map 安全发布,或对相关访问统一同步;需要共享并发更新时通常使用 ConcurrentHashMap。换成并发容器后,“先查再改”等业务复合操作仍要另外保证原子性。

不同键也可能互相覆盖

以 OpenJDK 25 的空桶插入路径作简化推演:键 A、B 不相等,但映射到同一个桶。两个线程都读取到桶为空,各自创建节点,随后先后把自己的节点赋给这个桶。后一次赋值可能覆盖前一次的入口,造成一条映射不可达。

这与单线程正常覆盖“同一个键的旧值”不同:这里即使是不同键,也会因共享桶的检查与修改没有同步而丢数据。类似竞争还可能发生在链表尾部修改和扩容迁移中。

计数也有独立问题。两个线程各新增一条映射,size 的递增却不是原子读改写,可能只增加一次。因此不能根据最终 size 判断所有插入都完整,也不能把“压测时数量刚好正确”当成线程安全证明。以上是操作交错的机制推演,具体故障是否出现取决于版本与执行时序。

为什么 get 和遍历也不能忽略

并发写入而读取方没有建立必要的 happens-before 关系时,不保证能看到期望的更新;扩容期间还涉及旧表、新表和节点迁移,普通 HashMap 没有为并发读取设计完整协议。

HashMap 迭代器在发现不符合约定的结构修改时,可能抛出 ConcurrentModificationException。但 fail-fast 是尽力诊断,既不是锁,也不保证每次竞争都被发现;没有抛异常并不表示结果正确。即使单线程在遍历期间直接向 Map 新增映射,也可能触发它,因此该异常本身也不能证明一定存在多个线程。

追问:只给引用加 volatile,或者只锁 put 行不行

把 Map 引用声明为 volatile,可以参与对象的发布和引用替换,但不会让后续对同一个 HashMap 的桶、链表和 size 修改自动变成原子操作。

只让写线程使用一把锁,而读线程仍无同步访问,也不普遍成立。应让相关访问遵循同一同步协议,例如统一使用同步包装并按要求持锁遍历;或者使用 ConcurrentHashMap,并用其原子方法完成单键的条件更新。多个键之间的业务不变量仍需要更大范围的协调。

如果数据在发布后确实不再修改,完成构建后通过合适方式安全发布的 HashMap 可以供多线程读取;“只读”的前提不能由“这个方法只调用 get”替代,仍需确认其他路径不会修改同一实例。

参考资料

相关面经

7 篇

字节跳动

后端开发工程师
一面
查看原面经 →
HashMap 为什么线程不安全?线程安全的替代方案如何实现?

靖安科技

后端工程师(RAG/Agent 项目)
秋招一面
查看原面经 →
追问
多个请求同时修改一个 HashMap 会有什么问题

阿里巴巴

AI 应用开发工程师
秋招一面
查看原面经 →
HashMap 在并发环境下可能出现什么问题?

知乎

全栈开发工程师
秋招一面
查看原面经 →
hashmap理解,是否安全

公司未注明

后端开发工程师
查看原面经 →
3hashmap为什么不是线程安全的
展开剩余 2 篇面经

京东

后端开发工程师(Agent)
实习一面
查看原面经 →
在并发场景下用HashMap会有什么问题?

美团

全栈开发工程师
一面
查看原面经 →
HashMap是否是线程安全

相关题目

4 题