ConcurrentModificationException异常的解决方案
ConcurrentModificationException
异常
ConcurrentModificationException
异常是并发修改异常,最常见的出现情况是:在多线程的程序中,一边使用迭代器遍历,一边又对迭代器种的元素进行修改
- 异常产生背景
在上设计模式的课程中,老师以飞机大战为题材让我们熟悉运用设计模式,其中在写玩家飞机的子弹和敌机的碰撞函数中,我看到老师是直接使用普通的for循环,代码如下:
for (int i = 0; i < enemyPlanes.size(); i++) { EnemyPlane enemyPlane = enemyPlanes.get(i); for (int j = 0; j < fires.size(); j++) { Fire fire = fires.get(j); if ((fire.x + fire.image.getWidth() / 4) > enemyPlane.x && fire.x < (enemyPlane.x + enemyPlane.image.getWidth())&& fire.y < (enemyPlane.y + enemyPlane.image.getHeight()) && (fire.y+fire.image.getHeight()/4) > enemyPlane.y ) { //子弹打中敌机,子弹和敌机同时消失 enemyPlane.remove(); fire.remove(); score++; } } }
我就自认为可以使用增强for循环进行简化书写,代码如下:
for (EnemyPlane enemyPlane : enemyPlanes) { for (Fire fire : fires) { if ((fire.x + fire.image.getWidth() / 4) > enemyPlane.x && fire.x < (enemyPlane.x + enemyPlane.image.getWidth())&& fire.y < (enemyPlane.y + enemyPlane.image.getHeight()) && (fire.y+fire.image.getHeight()/4) > enemyPlane.y ) { enemyPlane.remove(); fire.remove(); score++; } } }
结果在玩了一会游戏,就疯狂报
ConcurrentModificationException
异常了,结果如下:
-
异常产生原因分析:
要想了解这个异常的产生原因,就不得不了解增强for循环的底层实现原理了,也就是说得先了解迭代器(Iterator)!迭代器的设计原则是遵循:隔离性、独立性
- 隔离性: 如果集合增加、删除了元素,不能影响到已存在的迭代器
- 独立性 :是指不同迭代器遍历元素互不影响
这两种设计原则的目的就是为了保障迭代器的工作的规范性
而本题的问题就是出在迭代器的隔离性原则上,而实现隔离性原则有两种方式:
- 方式一:每次获取迭代器,都将待迭代的对象中的数据复制一份到迭代器中
- 方式二:在迭代器的类中设置一个成员变量,用来标记迭代器是否发生改变(这种方式有个专业名词:fail-fast机制)
显然方式二要优于方式一,Java官方也是采用方法二实现迭代器的隔离性原则的,见源码:
知道这些后,就能很清楚的明白为什么在hit方法中使用迭代器后,游戏进行一段时间就会报ConcurrentModificationException
异常了吧(●ˇ∀ˇ●),原因如下:
当游戏开始时,创建了一个迭代器,迭代器中有固定的元素了,然后当我们在期间进行了碰撞,进行了remove方法后子弹集合和敌机集合发生了改变,然后就导致迭代器遍历触发了checkForComodification()
函数,导致直接报ConcurrentModificationException
异常
-
解决方案:
- 方案一:改用普通for循环
拓展:
如果只是想修改一次集合的数据,并且不想报错,可以采用迭代器自带的remove
方法import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * @author ghp * @date 2022/9/14 */ public class Test { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); Collections.addAll(list,1,2,3,4,5,6); Iterator<Integer> listIterator = list.iterator(); System.out.println("修改前:list = " + list); while(listIterator.hasNext()){ //读取当前遍历的数据 int n = listIterator.next(); if(n == 3 || n == 6){ //当遍历到集合数据为3时,将该元素从集合中移除(然后会直接终止遍历) listIterator.remove(); } } System.out.println("修改后:list = " + list); } }
参考文章: