ConcurrentModificationException 是什么
- ConcurrentModificationException 是 Java 中运行时异常的一种,当在遍历集合时修改了集合(如添加、删除、或修改元素)没有正确处理时,就会抛出该异常。
常见场景
遍历中修改元素
- 使用 Iterator、for 等进行遍历时,直接通过集合的 add() 或 remove() 修改元素
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String item : list) {
if ("A".equals(item)) {
list.remove(item); // 抛出 ConcurrentModificationException
}
}
多线程环境中并发修改
- 线程环境下,线程 A 遍历集合时,线程 B 对集合进行了修改
为什么会发生 ConcurrentModificationException
结构性修改
- 了解为什么会发生前,我们需要先知道什么是结构性修改,在 Java 中,对集合类(如 ArrayList、HashSet 等)改变集合的元素数量,如添加或删除元素称为结构性修改。
modCount 机制
- Java 中的集合类有一个用于检测结构性修改的机制 - modCount 机制,每次结构性修改都会改变集合内部的 modCount 值。当使用迭代器遍历集合时,Iterator 会记录 modCount 的初始值。如果在遍历过程中集合的 modCount 被其他操作改变,迭代器会检测到不一致,抛出 ConcurrentModificationException。
处理方案
方案 1:使用 Iterator 的 remove() 方法
- Iterator 提供了安全的删除方法,可以在遍历过程中修改集合而不会引发异常。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("A".equals(item)) {
iterator.remove(); // 使用 Iterator 的 remove 方法,安全删除
}
}
System.out.println(list); // 输出: [B, C]
方案 2:使用 removeIf 方法(推荐)
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// 移除元素条件:删除元素值为 "A" 或 "C"
list.removeIf(item -> "A".equals(item) || "C".equals(item));
// 输出结果
System.out.println(list); // 输出: [B, D]
方案 3:使用 Stream 和 filter()(推荐)
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list = list.stream()
.filter(item -> !"A".equals(item)) // 过滤掉不需要的元素
.toList(); // Java 16+ 可以直接使用 .toList()
System.out.println(list); // 输出: [B]
方案 4:通过 Collections.synchronizedList 同步处理(推荐)
- 如果集合需要在多线程中修改,可以通过 Collections.synchronizedList 包装集合,确保线程安全(或其它线程安全实现)。
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("A");
list.add("B");
synchronized (list) {
for (String item : list) {
if ("A".equals(item)) {
list.remove(item); // 安全
}
}
}
System.out.println(list); // 输出: [B]
总结
- 当我们在实际开发过程中,我们应该尽量避免在避免在遍历过程中做结构性修改操作,从而避免 ConcurrentModificationException 异常。
- 如果业务场景确实需要修改集合元素,我们可以根据具体场景选择合适的方式,对于需要删除列表元素场景我们应该使用安全的删除方法,推荐方案二或方案三,对于多线程修改同一集合场景,使用方案四类似的线程安全操作来实现。
个人简介
👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.
🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。
🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。
💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。
🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。
📖 保持关注我的博客,让我们共同追求技术卓越。