【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:
所以,所有Collection的实现都会对Iterable接口中的Iterator<T> iterator();
进行实现。
以ArrayList为例,如图2所示,集合中有内部类对Iterator进行了实现。
图2:
比较重要的代码行我在图2中用红框标识了出来,其中,modCount
其实ArrayList中的一个参数,这个参数在每次调用集合的增,删类型的方法时,会改变这个参数的数值。
每当一个ArrayList的对象调用iterator()
方法时,就会立即以方法调用时ArrayList对象的mocCount
为参数构造一个新的迭代器。
在迭代器获取集合的下一个元素时,会执行checkForComodification()
方法,这个方法的代码如图3所示,这个方法内部主要就是比较当前ArrayList对象的modCount与获取迭代器时存储的expectedModCount是否相等,不相等就抛出ConcurrentModificationException;
图3:
回到示例代码一中,我们来分析一下modCount的变化。
先是调用了5次add方法,此时list对象中的modCout=5
。紧接着是增强for,由于底层是迭代器,所以立即构造了一个迭代器,迭代器内部expectedModCount=5
。
在遍历时调用了list对象的删除方法,导致list对象的modCount=6
,而迭代器中expectedModCount=5
,如图4所示,两者不相等,抛出异常。
图4:
另外,迭代器的删除方法不会存在这个问题,如图5所示,迭代器内部的删除代码调用集合的删除方法过后,会更新一次expectedModCount
使之与modCount
相等。
图5:
努力生长吧
感谢耐心看完。
欲速则不达,每天进步一点点。
原来如此 我悟了
踩过
大佬,贴贴
悟了悟了
考古