出现该异常场景
ConcurrentModificationException顾名思义就是并发修改异常。
我们先看2段出现异常的代码
第一段代码:迭代器遍历的时候调用list.remove删除元素
static List<String> list = new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
list.add(UUID.randomUUID().toString().substring(0,8));
}
printAndRemove();
}
public static void printAndRemove() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String data = iterator.next();
System.out.println(data);
list.remove(data);
}
}
第二段代码:多线程情况下往集合添加元素的时候,去输出集合
static List<String> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
// 定义5个线程同时对list进行写操作
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}
}).start();
}
}
以上2段代码都会出现Java ConcurrentModificationException
异常,第一段代码异常发生在遍历的时候取下一个元素的时候iterator.next()。第二段代码发生在输出的 时候System.out.println(list)
,其实直接输出集合,也是调用了list.toString方法,调用了迭代器,发生在iterator.next()这个代码出现异常。
我们可以猜想下出现ConcurrentModificationException异常的时候,
1.在使用迭代器遍历的时候,使用集合的增删改方法对集合进行更新操作。
出现该异常的原因
先看异常信息:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.fangyajun.javasduty.juc.collection.ArrayListTest.printAndRemove(ArrayListTest.java:31)
at com.fangyajun.javasduty.juc.collection.ArrayListTest.main(ArrayListTest.java:26)
看异常信息,找出出现异常的源码(指的JDK源码)位置,自下而上看源码信息,范围由大到小,
最后出现异常的代码是:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
-
查看源码得知,当
modCount != expectedModCount
成立的时候就会出现该异常, -
下面我们分析下什么情况下会出现
modCount != expectedModCount
,我们先来了解下modCount 和 expectedModCount的含义,查看源码得知:modCount:
是AbstractList的成员变量,ArrayList继承了AbstractList,所以modCount是ArrayLisl的成员变量。
expectedModCount:
是ArrayList内部类Itr的成员变量(集合别地方也定义了局部变量expectedModCount)- 查阅源码得知,在对集合进行add,remove,修改的时候都会对modCount+1操作,所以modCount就是对集合更新次数的一个记录,
- 而expectedModCount是在迭代器进行初始化的时候使
final int expectedModCount = modCount;
,所以,当我们在调用迭代器遍历的过程中有对使modCount+1操作发生,都会使modCount != expectedModCount
成立, - 通过分析得知,可以很容易得出,一个集合在遍历的过程中,如果对集合进行新值,新增,修改,删除操作都会发生ConcurrentModificationException异常
综合以上分析:
-
当集合在进行一个操作(比如遍历,排序)的过程中,我们对集合进行了更新(新值,修改,删除都是更新)操作,那么就会发生ConcurrentModificationException异常。
-
有的文章会带你分析迭代器的源码,说迭代器各种指针变动,其实出现该异常,和迭代器本身没有关系,迭代器的作用就是遍历,主要是迭代器定义了expectedModCount变量,只要在迭代的过程中,引发了modCount+1操作的发生,就会发生异常。
-
除了迭代器遍历操作,还有对集合排序,对集合元素替换等操作的过程中只要发了modCount != expectedModCount都会发生异常,可以看下面的ArrayList源代码就知道(不作分析,可以秒懂):
@Override @SuppressWarnings("unchecked") public void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); final int expectedModCount = modCount; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { elementData[i] = operator.apply((E) elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } @Override @SuppressWarnings("unchecked") public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
解决该异常的方法
通过分析我们知道原因了,主要是在操作集合的过程中,我们对就行了更新操作,那么我们如何来防止该异常的发生呢?
各个场景不一样,解决办法也不一样:
-
如果是使用迭代器遍历的时候,删除元素,那么我们不要使用集合本身的remove方法进行删除,而是使用迭代器的方法进行删除代码如下:
public static void printAndRemove() { Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String data = iterator.next(); System.out.println(data); iterator.remove(); } }
为什么使用迭代器的remove()方法不会发生异常呢,是因为迭代器的删除不会引发modCount+1操作,他是迭代器内部的操作,删除的是已经迭代器上一个已经遍历的元素。
-
如果是多线程先下发生该异常,我们可以使用并发安全类(CopyOnWriteArrayList等)