Exception in thread “Thread-2“ java.util.ConcurrentModificationException异常的解决方案

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);
    }
}

在这里插入图片描述


参考文章:

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识汲取者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值