故障原因
ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常
故障代码
public class ArrayListTest {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("a");
lists.add("b");
Iterator<String> iterator = lists.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next == "b") {
lists.remove(next);
}
}
}
}
ArrayList 会抛出异常的原因
在 ArrayList 源码里的内部类 Itr 的 next 方法中有一个 checkForComodification 方法,代码如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount 是修改记录数,expectedModCount 是期望修改记录数,初始化时expectedModCount = modCount。
这里会首先检查 modCount 是否等于 expectedModCount。modCount 是保存修改次数,每次我们调用 add、remove 或 trimToSize 等方法时它会增加,expectedModCount 是迭代器的变量,当我们创建迭代器时会初始化并记录当时的 modCount。后面迭代期间如果发现 modCount 和 expectedModCount 不一致,就说明有人修改了集合的内容,就会抛出异常。
和 ArrayList 不同的是,CopyOnWriteArrayList 的迭代器在迭代的时候,如果数组内容被修改了,CopyOnWriteArrayList 不会报 ConcurrentModificationException 的异常,因为迭代器使用的依然是旧数组,只不过迭代的内容可能已经过时了
解决方案
解决方案1
使用 iterator 的 remove 操作替代 ArrayList 集合自己的 remove 操作
public class ArrayListTest {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("a");
lists.add("b");
Iterator<String> iterator = lists.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next == "b") {
iterator.remove();
}
}
}
}
分析:通过查看 iterator 的 remove 方法发现,其实还是调用了 ArrayList 集合的remove方法移除元素,但是会使 expectedModCount = modeCount 所以不会抛出 ConcurrentModificationException 异常
解决方案2
使用 CopyOnWriteArrayList 并发集合类
public class ArrayListTest {
public static void main(String[] args) {
List<String> lists = new CopyOnWriteArrayList<>();
lists.add("a");
lists.add("b");
Iterator<String> iterator = lists.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next == "b") {
lists.remove(next);
System.out.println(lists);
}
}
}
}
因为迭代器中没有 checkForComodification 操作,并且集合的 add 和 remove 方法中都通过 ReentrantLock 加锁保证并发操作下的安全性。
在对ArrayList集合进行并发操作时尽量使用CopyOnWriteArrayList集合类代替
网友评论