ConcurrentModificationException是Java集合fail-fast机制触发的主动保护异常,源于modCount与expectedModCount校验失败;单线程遍历时须用迭代器remove()而非集合remove(),多线程场景应使用ConcurrentHashMap替代HashMap。

Java 中 HashMap 本身不支持多线程并发修改,一旦在遍历过程中被其他线程(或同一线程非迭代器方式)结构性修改,就会立即抛出 ConcurrentModificationException。这不是偶然错误,而是 fail-fast 机制的主动保护行为——它通过 modCount 和 expectedModCount 的校验,在问题发生早期就中断执行,避免更隐蔽的数据错乱或死循环。
单线程遍历时删除元素的正确做法
即使没有多线程,for-each 或增强 for 循环中直接调用 map.remove(key) 也会触发该异常,因为迭代器已感知到结构变化。
- 必须使用迭代器自身的
remove()方法:它会在删除后同步更新expectedModCount = modCount,维持一致性 - 示例:
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String k = it.next();
if ("target".equals(k)) it.remove(); // 安全
} - Java 8+ 可用
replaceAll()、computeIfAbsent()等函数式方法规避显式迭代
多线程环境下不能用普通 HashMap
多个线程同时 put、remove 或触发扩容(resize),会导致:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- JDK 7:头插法 + 并发扩容 → 链表成环 →
get()死循环、CPU 100% - JDK 8:虽改用尾插,但无锁竞争 → 数据覆盖、丢失、
null值返回、ConcurrentModificationException - 所有版本:fail-fast 校验失败,遍历时必然报错
推荐的线程安全替代方案
不要用 Hashtable(粗粒度锁,性能差)或 Collections.synchronizedMap()(遍历仍需手动同步,易漏)。
立即学习“Java免费学习笔记(深入)”;
-
首选
ConcurrentHashMap:JDK 8 起采用synchronized+ CAS 细粒度锁(按桶加锁),读操作无锁,写冲突概率低;遍历时基于弱一致性快照,不会抛异常 - 注意差异:
size()是估算值,应改用mappingCount();containsValue()不保证看到最新写入 - 如需复合原子操作(如“查无则加载并写入”),用
computeIfAbsent(),比手写双重检查更可靠
临时方案与误用提醒
若因历史代码难以改造,不得已使用 Collections.synchronizedMap(new HashMap<>()),务必记住:
- 所有遍历操作(包括
for-each)必须包裹synchronized(map)块,否则仍可能抛异常 - 它只是给每个方法加了同一把锁,吞吐量远低于
ConcurrentHashMap - 不要试图用
ReentrantLock手动包装 HashMap——极易漏锁、死锁,且无法解决扩容时的底层结构风险

















