HashMap非线程安全,多线程下易出错;推荐优先使用ConcurrentHashMap(CAS+synchronized单桶锁、原子操作、渐进扩容);低并发可用Collections.synchronizedMap;读多写少可选ReadWriteLock+HashMap;只读场景用不可变Map。

HashMap 本身不是线程安全的,直接在多线程环境下使用会导致数据覆盖、size 不准、死循环(JDK 1.7)、ConcurrentModificationException 等问题。要实现线程安全,不能靠“自己加锁保护所有调用”,而应根据场景选择合适、标准、经过充分验证的方案。
优先用 ConcurrentHashMap
这是最常用、最推荐的方式,尤其适合读写混合、高并发场景:
- JDK 8 起采用“CAS + synchronized 锁单个桶头节点”机制,写操作只锁冲突的桶,读操作完全无锁,性能远高于全表同步
- 提供原子性复合操作,比如 putIfAbsent、computeIfAbsent、remove(key, value),避免了“先查后改”的竞态问题
- 扩容过程是渐进式、线程安全的,不会出现环形链表或死循环
- 无需额外包装或手动同步,开箱即用:Map<String, Integer> map = new ConcurrentHashMap<>();
低并发且简单场景可用 Collections.synchronizedMap
适合线程数少、吞吐量低、代码逻辑简单的情况(如配置缓存、初始化后只读写几次):
- 底层是对所有 public 方法加 synchronized(this),等价于给整个 Map 对象上一把大锁
- 性能较差,尤其在写多或并发高时会成为瓶颈
-
注意:迭代器仍需手动同步,否则可能抛 ConcurrentModificationException
示例:synchronized (map) { for (Entry<K,V> e : map.entrySet()) { ... } } - 不支持原子性复合操作,像“若不存在则 put”必须手动加锁包裹
读多写少且需定制控制时考虑 ReadWriteLock + HashMap
适用于读操作极频繁、写操作极少,且你愿意承担更高复杂度和出错风险的场景:
- 读锁允许多个线程并发读,写锁独占,比 synchronizedMap 吞吐更高
- 必须严格保证:所有读都走 readLock,所有写都走 writeLock,且每次 lock 后必须在 finally 中 unlock
- 无法规避 HashMap 自身的结构性风险(如扩容中被读到中间状态),也不提供原子方法,需自行保障逻辑正确
- 一般建议仅在已有 HashMap 且改造成本高、又明确压测证实 synchronizedMap 成为瓶颈时才选用
只读共享场景直接用不可变 Map
如果 Map 构建完成后就不再修改(如加载配置、枚举映射表),这是最安全、最轻量的选择:
- JDK 9+ 推荐用 Map.of(k1,v1,k2,v2) 或 Map.copyOf(originalMap)
- Guava 提供 ImmutableMap.builder().put(...).build(),更灵活
- 不可变对象天然是线程安全的——没有可变状态,无需同步,无内存可见性问题
- 注意:一旦尝试修改(如调用 put),会立即抛 UnsupportedOperationException


















