【Java】20220316_Java 集合 _ 并发修改异常

20220316_Java集合_并发修改异常


前言

本文一起和各位Java工程师讨论一下ConcurrentModificationException——并发修改异常。

复现异常代码

如果执行示例代码一,很容易就会“喜获”这个异常。

示例代码一:

import java.util.ArrayList;
import java.util.List;

/**
 * @author LLaamar
 * @date 2022/3/16 23:19
 */
public class ConcurrentModificationExceptionTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(10);
        list.add("pwl");
        list.add("icu");
        list.add("fish");
        list.add("pi");
        list.add("皮");

        for (String s : list) {
            if("皮".equals(s))
                list.remove(s);
        }
        System.out.println(list);
    }
}

执行示例代码一的异常结果:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at ConcurrentModificationExceptionTest.main(ConcurrentModificationExceptionTest.java:17)

代码优化

如果将示例代码一中的代码修改一下,如示例代码二所示,使用迭代器来遍历,用迭代器来删除,那么代码就能正常运行,输出正确的结果。

示例代码二:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author LLaamar
 * @date 2022/3/16 23:19
 */
public class ConcurrentModificationExceptionTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(10);
        list.add("pwl");
        list.add("icu");
        list.add("fish");
        list.add("pi");
        list.add("皮");
        Iterator<String> ir = list.iterator();
        while (ir.hasNext()){
            if("皮".equals(ir.next()))
                ir.remove();
        }
        System.out.println(list);
    }
}

示例代码二的执行结果:

[pwl, icu, fish, pi]

问题思考

在使用增强for循环遍历集合时,其底层仍然使用迭代器来作为遍历依据,那么两个示例代码遍历方式可以说是一样的,但为什么输出结果不一样呢?

阅读一番集合源码以后,我悟了。

如图1所示,Collection接口继承了Iterable接口。

图1:

image-20220316233636418.png

所以,所有Collection的实现都会对Iterable接口中的Iterator<T> iterator();进行实现。

以ArrayList为例,如图2所示,集合中有内部类对Iterator进行了实现。

图2:

image-20220316234231077.png

比较重要的代码行我在图2中用红框标识了出来,其中,modCount其实ArrayList中的一个参数,这个参数在每次调用集合的增,删类型的方法时,会改变这个参数的数值。

每当一个ArrayList的对象调用iterator()方法时,就会立即以方法调用时ArrayList对象的mocCount为参数构造一个新的迭代器。

在迭代器获取集合的下一个元素时,会执行checkForComodification()方法,这个方法的代码如图3所示,这个方法内部主要就是比较当前ArrayList对象的modCount与获取迭代器时存储的expectedModCount是否相等,不相等就抛出ConcurrentModificationException;

图3:

image-20220316235035519.png

回到示例代码一中,我们来分析一下modCount的变化。

先是调用了5次add方法,此时list对象中的modCout=5。紧接着是增强for,由于底层是迭代器,所以立即构造了一个迭代器,迭代器内部expectedModCount=5

在遍历时调用了list对象的删除方法,导致list对象的modCount=6,而迭代器中expectedModCount=5,如图4所示,两者不相等,抛出异常。

图4:

image-20220316235932961.png

另外,迭代器的删除方法不会存在这个问题,如图5所示,迭代器内部的删除代码调用集合的删除方法过后,会更新一次expectedModCount使之与modCount相等。

图5:

image-20220317000235874.png

努力生长吧

感谢耐心看完。

欲速则不达,每天进步一点点。