应优先使用 ConcurrentHashMap 替代 HashMap:JDK 8 中采用 CAS + synchronized(锁单个桶),读无锁、写细粒度,语义兼容且彻底规避扩容死循环、数据覆盖等问题。

Java 中避免 HashMap 死循环扩容问题,核心是避开 JDK 7 及之前版本在多线程扩容时因头插法 + 无同步导致的环形链表。虽然 JDK 8 已修复死循环风险,但 HashMap 本身仍不支持并发写入。关键不在“怎么修 HashMap”,而在于“怎么用对场景”。
优先用 ConcurrentHashMap 替代
这是最直接、最安全、最符合工程实践的方案:
- ConcurrentHashMap 在 JDK 8 中采用 CAS + synchronized(锁单个桶),读操作无锁,写操作细粒度,性能远高于全局锁
- 语义与 HashMap 高度兼容:put/get/size/containsKey 等常用方法行为一致,迁移成本低
- 完全规避扩容死循环、数据覆盖、size 不准等所有并发问题,无需额外同步逻辑
禁用多线程写入 HashMap
如果因历史原因必须保留 HashMap,务必切断并发写入口:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 初始化后立即封装为只读视图:
Collections.unmodifiableMap(new HashMap(...)) - 声明为
static final,且仅在类加载时一次性构建,运行期绝不调用 put/remove - 若需动态更新,改用线程局部存储(ThreadLocal
控制扩容时机与规模
即使单线程使用,不合理扩容也会带来性能抖动;多线程下更会放大风险:
立即学习“Java免费学习笔记(深入)”;
- 预估容量并显式指定:
new HashMap(expectedSize / 0.75f + 1),避免频繁 resize - 避免小容量起步(如
new HashMap(2)),该配置极易在高并发下触发多线程同时扩容 - 监控实际 size 和 threshold,通过 JMX 或 Arthas 查看
HashMap.size和扩容频率
快速识别与应急止损
线上一旦出现疑似死循环,要能秒级判断:
- 用
jstack <pid></pid>搜索HashMap.get或getNode,若多个线程卡在e = e.next行,基本可确认 - 用
top -Hp <pid></pid>找出 CPU 占用最高的线程 ID,再用thread <id></id>(Arthas)看完整堆栈 - 临时降级:将可疑 HashMap 替换为
Collections.synchronizedMap(...)快速止血(注意这只是过渡,非长期方案)

















