HashMap本身不是线程安全的,也不会在并发读写时主动抛出异常,只会导致不可预测行为;要使其明确抛出ConcurrentModificationException等异常,需利用其迭代器fail-fast机制、Collections.synchronizedMap配合手动同步、自定义版本检测或JUC运行时工具。

Java 中的 HashMap 本身**不是线程安全的**,但它**不会在并发读写时主动抛出异常**——它只会产生不可预测的行为(如死循环、数据丢失、get 返回 null 等),而不是抛异常。
如果你希望在并发读写 HashMap 时**明确抛出异常(如 ConcurrentModificationException)**,需要借助特定手段来“触发”它的 fail-fast 机制,或者用其他方式主动检测并发修改。以下是几种常见且实用的方式:
1. 在迭代过程中被其他线程修改(触发 ConcurrentModificationException)
HashMap 的迭代器是 fail-fast 的:如果在迭代期间,有其他线程(或同一线程)通过 put/remove 修改了结构(如扩容、增删节点),迭代器下次调用 next() 或 hasNext() 就会抛 ConcurrentModificationException。
- ⚠️ 注意:这个异常只在迭代器使用中被修改时才可能触发,单纯多个线程同时
get或同时put(无迭代)通常不会抛异常,而是引发隐性 bug。 - ✅ 示例场景:
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
// 线程1:开始迭代
Iterator<Integer> it = map.values().iterator();
// 线程2:此时执行 map.put("b", 2); → 修改了 modCount
// 线程1:再次 it.next() → 抛 ConcurrentModificationException
2. 使用 Collections.synchronizedMap 包装后,仍手动迭代(不加锁)
Collections.synchronizedMap 保证每个方法是原子的,但迭代仍需手动同步。若忘记同步就迭代,且其他线程修改了 map,同样会触发 ConcurrentModificationException(因为底层仍是 HashMap 的迭代器)。
立即学习“Java免费学习笔记(深入)”;
- ✅ 正确做法:
synchronized(map) { for (Entry e : map.entrySet()) { ... } } - ❌ 错误做法:直接
for (Entry e : map.entrySet()),此时另一线程调用map.put()可能导致异常。
3. 主动检测并发修改(自定义 wrapper)
若想在任意读写操作(包括 get/put)中检测“非串行访问”,可封装一个带版本号或锁状态检查的代理 Map:
- 维护一个 volatile long version 或 AtomicBoolean isWriting;
- 每次写操作前检查是否已有其他写入未完成;
- 读操作也可检查是否有写入正在进行(实现读写锁语义);
- 不满足条件则直接
throw new IllegalStateException("Concurrent access detected")。
这种方式不是标准行为,但能按需“强制抛异常”,适合测试或调试阶段快速暴露并发问题。
4. 使用 JUC 工具辅助检测(如 ThreadSanitizer 或 JMC)
虽然不直接抛 Java 异常,但运行时工具可在 JVM 层面报告数据竞争(data race):
- OpenJDK 的
-XX:+UnlockDiagnosticVMOptions -XX:+VerifySilentMonitoring(有限); - 更推荐:使用 Thread Sanitizer for Java(如 Jetbrains 的 Concurrency Testing 插件、或 JMC 的飞行记录器 + 竞争分析);
- 单元测试中配合
net.jodah:concurrentunit库,可断言并发修改是否发生。
总之,HashMap 本身不会因并发读写自动抛异常;要让它“报错”,核心是利用其迭代器的 fail-fast 特性,或通过包装/监控手段主动拦截并抛异常。生产环境应优先选用 ConcurrentHashMap 或加锁,而非依赖异常来发现问题。


















