文章目录
- 一、问题描述
- 二、问题分析
- 三、问题解决
- 四、总结
本篇是对于集合类源码浅析のArrayList中第五部分并发修改异常原因分析
的扩展补充。
一、问题描述
我们首先看一段代码:
public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");for (String s : list) {if ("1".equals(s)) {list.remove(s);}}}
定义了一个集合,存入了三个元素,然后在增强for循环
中对元素进行删除,然后运行,毫无疑问地出现了ConcurrentModificationException
(并发修改异常)。这也是经典面试题中所说,不能一边遍历集合一边删除元素,如果必须要这么做,需要使用迭代器
。
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at com.light.gulimall.product.web.ItemController.main(ItemController.java:41)
下面我们从源码的角度对问题进行分析
二、问题分析
增强型 For 循环
是 For 循环的语法糖,经过编译后的class文件中,是这样:
public static void main(String[] args) {ArrayList<String> list = new ArrayList();list.add("1");list.add("2");list.add("3");Iterator var2 = list.iterator();while(var2.hasNext()) {String s = (String)var2.next();if ("1".equals(s)) {list.remove(s);}}}
我们跟踪进list.remove(s)
这行代码,发现最终每次执行一次remove操作,AbstractList
抽象类的成员变量modCount都会+1。
从modCount
的注释上也可以看出,新增,删除等操作,都会导致这个变量的值增加:
再次观察报错时的堆栈信息,发现问题出现在java.util.ArrayList$Itr.next(ArrayList.java:851)
和java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
:
第一个Itr.next
,对应的是编译后class文件的
继续跟进,进入java.util.Iterator
接口,选择如图的实现:
会发现在执行移动指针的的逻辑之前,先进入checkForComodification()
方法,也就是先前报错信息中的Itr.checkForComodification
在checkForComodification()
方法中会进行判断,将当前的modCount
和expectedModCount
进行比较,如果不一致就会抛出最开始的异常。
而expectedModCount
是Itr的一个成员变量,它的值是modCount
,并且后续不会对此变量的值进行更新,只在构造Itr对象时进行了初始化。
每次进行添加元素,删除元素时,modCount
都会+1,而expectedModCount
不会改变,必然两者不会相等,所以在移动指针时校验不通过,出现经典的ConcurrentModificationException
(并发修改异常)。
三、问题解决
通过上面的分析,想必已经对为什么会出现并发修改异常,有了一定的认识,那么如何避免这样的问题?答案是可以使用迭代器:
public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");Iterator<String> it = list.iterator();while (it.hasNext()) {String element = it.next();it.remove();}System.out.println(list);}
执行后没有发生异常,并且集合中的元素最终被清空,原因在于,使用了迭代器的remove()
方法,会在调用集合的remove()
方法后,将最新的modCount
重新赋值给expectedModCount
,所以在下一次执行it.next()
移动指针时,校验两者是否相等则不会抛出异常。
四、总结
集合类中,modCount
字段设计的目的就是为了标记修改的次数,从而保证快速失败的机制。子类可以自由选择是否使用这个字段。官方也是不推荐一边遍历集合,一边删除元素的操作。如果一定要,可以使用迭代器避免并发修改异常。