concurrentmodificationexception是因fail-fast机制触发,单线程遍历时调用集合自身remove()也会抛出;其核心是modcount与expectedmodcount不一致。

Java中ConcurrentModificationException(并发修改异常)不是因为多线程真正“并发”修改了集合,而是检测到迭代过程中集合结构被意外修改——哪怕单线程下边遍历边调用remove()或add()也会触发。
为什么单线程也会抛这个异常?
核心在于fail-fast(快速失败)机制:大多数集合(如ArrayList、HashMap、HashSet)内部维护一个modCount(修改计数器),每次结构修改(增、删、清空)就+1;而迭代器在创建时会记录当时的expectedModCount。遍历时若发现两者不一致,立刻抛出ConcurrentModificationException。
例如:
List<string> list = new ArrayList(Arrays.asList("a", "b", "c"));
for (String s : list) {
if ("b".equals(s)) {
list.remove(s); // ❌ 触发异常:迭代中直接调用list.remove()
}
}</string>
安全删除元素的正确方式
不能在增强for循环或普通迭代中直接调用集合的remove(),应使用以下方法:
-
用迭代器的
remove()方法:Iterator.remove()会在删除后同步更新expectedModCount,保证一致性。 - 倒序for循环 + 索引删除:避免因删除导致后续元素前移而跳过元素,且不依赖迭代器。
-
收集待删元素,遍历结束后批量删除(如用
removeAll())。
示例(推荐):
Iterator<string> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("b".equals(s)) {
it.remove(); // ✅ 安全
}
}</string>
多线程场景下的替代方案
如果确实需要多个线程同时读写集合,应放弃非线程安全的集合,改用:
-
CopyOnWriteArrayList:适合读多写少,写操作复制整个数组,迭代器基于快照,不会抛CME。 -
ConcurrentHashMap:分段锁或CAS实现,支持高并发读写,keySet()/values()返回弱一致性视图。 -
BlockingQueue系(如LinkedBlockingQueue):适用于生产者-消费者模型,自带线程安全与阻塞控制。
注意:Vector和Hashtable虽线程安全,但方法级synchronized粒度太粗,性能差,不推荐新代码使用。
调试与规避小技巧
遇到CME时可从这几处排查:
- 检查是否在
for-each、stream().forEach()或iterator()循环体内调用了集合自身的修改方法。 - 确认是否多个线程共用同一集合实例,且未加同步或未换用并发集合。
- 留意间接修改:比如把集合传给某个方法,该方法内部做了
clear()或addAll()。 - 临时调试可用
new ArrayList(originalList)创建副本再遍历,避免干扰原集合。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











