HashMap线程不安全,多线程put/remove易致数据覆盖、JDK7扩容死循环、ConcurrentModificationException;推荐用ConcurrentHashMap(JDK8+ CAS+synchronized细粒度锁),或按场景选synchronizedMap、显式锁、不可变Map。

HashMap 在 Java 中不是线程安全的,多线程同时写入(如 put、remove)可能引发数据错乱、死循环(JDK 7 中扩容链表形成环)、或 ConcurrentModificationException(迭代时被修改)。要安全处理并发写入,不能靠同步包装或临时加锁“修修补补”,而应根据场景选择真正适合的线程安全方案。
用 ConcurrentHashMap 替代 HashMap
这是最常用、推荐的方案。ConcurrentHashMap 从 JDK 5 起就专为高并发读写设计,内部采用分段锁(JDK 7)或 CAS + synchronized 细粒度加锁(JDK 8+),支持高并发读、相对高效的写,且保证原子性操作(如 computeIfAbsent、merge、replace)。
- 直接替换:把
Map改为map = new HashMap(); ConcurrentMapmap = new ConcurrentHashMap(); - 避免用
putIfAbsent+ 手动判断逻辑,优先使用computeIfAbsent(k, func),它天然原子 - 注意:它的
size()返回的是估算值(非强一致性),如需精确计数,可配合mappingCount()(返回 long,更可靠)
读多写少且写操作可串行化时,用 Collections.synchronizedMap
适用于写操作极少、但需要严格顺序和强一致性读写的简单场景(例如配置缓存初始化后只读)。它对整个 map 加 synchronized,吞吐量低,不推荐高频写入。
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
- 写法:
MapsyncMap = Collections.synchronizedMap(new HashMap()); - ⚠️ 迭代必须手动同步:
synchronized(syncMap) { syncMap.keySet().iterator()... },否则仍可能抛出ConcurrentModificationException - 适合一次性加载 + 只读访问,或调试/测试中快速兜底,生产高频并发慎用
写操作有复杂逻辑且需强一致性时,考虑显式锁 + 普通 HashMap
当业务逻辑涉及多个 map 操作(如先查、再算、再删、再 put 多个键)、且必须原子执行时,ConcurrentHashMap 的单方法原子性不够用。此时可用 ReentrantLock 或 synchronized 包裹整个逻辑块,底层仍用 HashMap。
- 示例:统计频次并清理过期项,需保证“读旧值 → 计算新值 → 写入 → 删除”不被中断
- 锁粒度尽量小,避免锁住整个 map 对象;可考虑按 key 分片加锁(如
lock[key.hashCode() & (n-1)])提升并发度 - 注意死锁风险:统一加锁顺序,避免嵌套调用其他锁持有方法
纯只读场景,用不可变 Map 提升安全性与性能
如果 map 构建完成后完全不修改(如加载配置、枚举映射表),应转为不可变结构,彻底规避并发问题,还能节省内存和 GC 开销。
- JDK 10+ 推荐:
Map或immutable = Map.of(k1,v1,k2,v2); Map.copyOf(originalMap) - Guava 用户可用:
ImmutableMap.copyOf(map),构建时校验 null,运行时绝对安全 - 注意:任何修改操作都会抛
UnsupportedOperationException,适合明确生命周期的静态数据
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










