Java 并发修改异常(ConcurrentModificationException)问题详解

在多线程环境中,并发修改异常(ConcurrentModificationException 是一种常见的运行时异常,通常发生在我们在遍历集合时,集合的结构被同时修改。这个问题通常出现在使用 Iterator 或增强型 for 循环(foreach)时,集合在遍历的过程中被结构性修改(如添加、删除元素)。


1. 什么是并发修改异常

ConcurrentModificationException 是 Java 集合类中的一个异常,它表示在 遍历集合的同时修改 集合结构(例如添加、删除元素),而这种修改操作与遍历操作之间没有同步机制时,抛出的异常。

典型场景:

  • 使用 Iterator 或增强型 for 循环遍历集合时,修改集合(例如 remove()add())会导致并发修改异常。

2. 引发 ConcurrentModificationException 的场景

2.1 使用增强型 for 循环遍历集合时修改集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

for (String lang : list) {
// 在遍历过程中修改集合
if ("Python".equals(lang)) {
list.remove(lang); // 这里会抛出 ConcurrentModificationException
}
}
}
}

2.2 使用 Iterator 遍历集合时修改集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorConcurrentModificationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String lang = iterator.next();
// 在迭代过程中修改集合
if ("Python".equals(lang)) {
list.remove(lang); // 这里会抛出 ConcurrentModificationException
}
}
}
}

解释:在以上两个例子中,集合的结构在遍历过程中被修改。ArrayList 会维护一个 modCount 字段来记录结构的修改次数,当 Iterator 或增强型 for 循环的迭代器检测到集合的修改次数和它的 modCount 不一致时,就会抛出 ConcurrentModificationException


3. 如何避免并发修改异常

3.1 使用 Iteratorremove() 方法

如果在遍历集合时需要删除元素,可以使用 Iterator 提供的 remove() 方法。这种方法会在删除元素时保持迭代器的状态一致,从而避免并发修改异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorRemoveExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String lang = iterator.next();
if ("Python".equals(lang)) {
iterator.remove(); // 使用 iterator.remove() 删除元素
}
}

System.out.println(list); // [Java, C++]
}
}

3.2 使用 CopyOnWriteArrayList 等线程安全集合

如果你需要在多线程环境中进行集合的修改操作,可以使用线程安全的集合类,如 CopyOnWriteArrayListConcurrentHashMap。这些类在修改集合时会创建集合的副本,因此不会发生并发修改异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.concurrent.CopyOnWriteArrayList;

public class ConcurrentModificationSafeExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

for (String lang : list) {
if ("Python".equals(lang)) {
list.remove(lang); // 使用线程安全的 CopyOnWriteArrayList,删除时不会抛出异常
}
}

System.out.println(list); // [Java, C++]
}
}

解释CopyOnWriteArrayList 是线程安全的,它通过在修改集合时创建一个副本来确保在遍历时不会出现并发修改异常。它适用于遍历频繁但修改较少的场景。

3.3 使用 forEachRemaining() 方法

Iterator 提供了 forEachRemaining() 方法,可以在遍历时修改集合的元素。通过这种方式,可以避免在迭代时直接修改集合的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorForEachRemainingExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

Iterator<String> iterator = list.iterator();
iterator.forEachRemaining(lang -> {
if ("Python".equals(lang)) {
iterator.remove(); // 正确的删除方式
}
});

System.out.println(list); // [Java, C++]
}
}

4. 总结

  • 并发修改异常:当在遍历集合的过程中修改集合的结构时(如 add()remove()),可能会抛出 ConcurrentModificationException
  • 避免方式
    • 使用 Iteratorremove() 方法来安全删除元素。
    • 使用线程安全的集合类,如 CopyOnWriteArrayList,避免并发修改异常。
    • 使用 forEachRemaining() 方法遍历 Iterator 时修改集合。

并发修改异常是一种常见的错误,正确的做法是通过提供合适的 API(如 Iterator.remove())或者使用线程安全的集合类来避免它的发生。