4.ConcurrentModifyException的产生原因及如何避免

本文深入探讨了ConcurrentModificationException产生的原因及其在单线程和多线程环境下的触发场景,并提供了有效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4.ConcurrentModifyException的产生原因及如何避免

在我做的一个模块中,会用到遍历一个集合类,遍历的同时根据条件判断集合中的对象,如果不符合条件则将该对象从集合中移除。这种情况很容易产生ConcurrentModificationExceptionException,这个异常会导致程序停止继续运行,所以遇到这个异常必须要处理来保证程序正确运行。

1 关于ConcurrentModificationException

ConcurrentModificationException这个异常是从JDK1.2时就存在。当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。这个异常在单线程和多线程运行环境都可以产生。

某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该Collection。通常在这些情况下,迭代的结果是不确定的。如果检测到这种行为,一些迭代器实现(包括JRE提供的所有通用collection实现)可能选择抛出此异常。

执行该操作的迭代器称为快速失败迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。

执行该操作的迭代器称为快速失败迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败操作会尽最大努力抛出ConcurrentModificationException。

因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。

2 单线程触发场景举例

2.1 单线程触发举例

如果线程使用快速失败迭代器在collection上迭代时直接修改该collection,则迭代器将抛出此异常。

触发场景 1.初始化集合类访入100个对象,并给这些对象的属性value值赋1-100的值。 2.遍历该集合类,判断对象的value值小于10则删除。 3.运行程序会抛出ConcurrentModificationException异常。

package com.dashidan.faq4;

import java.util.ArrayList;

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo1 {

    public static void main(String[] args) {
        /** 初始化集合类*/
        ArrayList<TestObj> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(new TestObj(i));
        }

        /** 遍历时删除元素*/
        for (TestObj obj : list) {
            if (obj.getValue() < 10) {
                /** 这里会抛出ConcurrentModificationException*/
                list.remove(obj);
            }
        }
    }
}

class TestObj {
    int value;

    public TestObj(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

运行结果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.dashidan.faq4.Demo1.main(Demo1.java:20)

看.抛出这个异常就是这么简单。

2.2 解决单线程环境的ConcurrentModificationException异常

单线程环境中可以通过将ArrayList集合改为CopyOnWriteArrayList,或者可以通过迭代器遍历删除,可以避免出现ConcurrentModificationException异常.

2.3 ArrayList集合改为CopyOnWriteArrayLis

示例代码:

package com.dashidan.faq4;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo2 {

    public static void main(String[] args) {
        /** 初始化集合类*/
        CopyOnWriteArrayList<TestObj> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(new TestObj(i));
        }

        /** 遍历时删除元素*/
        for (TestObj obj : list) {
            if (obj.getValue() < 10) {
                /** 这里不会抛出ConcurrentModificationException*/
                list.remove(obj);
            }
        }

        System.out.println();
    }
}
2.4 通过迭代器遍历删除

通过集合类的iterator()方法获取迭代器对象Iterator ,通过迭代器对象的iterator.hasNext()方法判断是否还有数据,如果有的话,通过iterator.next()方法得到下一个对象,然后通过iterator.remove()方法删除.在单线程环境中这样可以避免出现ConcurrentModificationException。

示例代码:

package com.dashidan.faq4;

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

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo3 {

    public static void main(String[] args) {
        for (int n = 0; n < 1000000; n++) {
            /** 初始化集合类*/
            ArrayList<TestObj> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                list.add(new TestObj(i));
            }

            /** 遍历时删除元素*/
            Iterator<TestObj> iterator = list.iterator();
            while (iterator.hasNext()) {
                TestObj testObj = iterator.next();
                if (testObj.getValue() < 10) {
                    iterator.remove();
                }
            }
        }
    }
}
2.5 这两种方式的效率比较

每个都执行一百万次比较时间:

  • Demo2测试代码
package com.dashidan.faq4;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo2 {

    public static void main(String[] args) {
        long t1 = System.currentTimeMillis();
        for (int n = 0; n < 1000000; n++) {
            /** 初始化集合类*/
            CopyOnWriteArrayList<TestObj> list = new CopyOnWriteArrayList<>();
            for (int i = 0; i < 100; i++) {
                list.add(new TestObj(i));
            }

            /** 遍历时删除元素*/
            for (TestObj obj : list) {
                if (obj.getValue() < 10) {
                    /** 这里不会抛出ConcurrentModificationException*/
                    list.remove(obj);
                }
            }
        }

        long t2 = System.currentTimeMillis();
        System.out.println("t2 - t1 " + (t2 - t1));
    }
}

输出结果:

t2 - t1 7013
  • Demo1测试代码
package com.dashidan.faq4;

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

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo3 {

    public static void main(String[] args) {
        long t1 = System.currentTimeMillis();
        for (int n = 0; n < 1000000; n++) {
            /** 初始化集合类*/
            ArrayList<TestObj> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                list.add(new TestObj(i));
            }

            /** 遍历时删除元素*/
            Iterator<TestObj> iterator = list.iterator();
            while (iterator.hasNext()) {
                TestObj testObj = iterator.next();
                if (testObj.getValue() < 10) {
                    iterator.remove();
                }
            }
        }
        long t2 = System.currentTimeMillis();
        System.out.println("t2 - t1 " + (t2 - t1));
    }
}

输出结果:

t2 - t1 1919

输出结果可能不一样,但可以从数据上看出第二种方法效率高于第一种方法.

单线程环境中推荐第二种方式,第二种方式的适应性适应性更广,继承自Collection类的集合类,都可以采用这种方式。

3 多线程触发举例

多线程情景中,一个线程遍历,一个线程修改,即使采用迭代器(Iterator)来遍历还是会出现ConcurrentModificationException异常,单线程情境下安全的操作,多线程情境中不再安全。

3.1 多线程触发步骤

1.初始化一个集合类。 2.启动一个线程随机删除数据。 3.主线程不停的遍历该集合。

3.2 示例代码
package com.dashidan.faq4;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo4 {

    public static void main(String[] args) {
        /** 初始化集合类*/
        ArrayList<TestObj> list = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            list.add(new TestObj(i));
        }

        /**启动一个线程随机删除数据*/
        new Thread(new ThreadClass(list)).start();

        /** 遍历元素*/
<TestObj> iterator = list.iterator();
        while (iterator.hasNext()) {
            TestObj testObj = iterator.next();
        }
    }
}


class ThreadClass implements Runnable {

    List<TestObj> list;

    public ThreadClass(List<TestObj> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            int index = new Random().nextInt(list.size());
            list.remove(index);
        }
    }
}
3.3 解决多线程环境的ConcurrentModificationException异常

将ArrayList改为CopyOnWriteArrayList在多线程环境中同样可以避免出现这个异常。

示例代码:

package com.dashidan.faq4;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo5 {
    public static void main(String[] args) {
        /** 初始化集合类*/
        ArrayList<TestObj> list = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            list.add(new TestObj(i));
        }

        CopyOnWriteArrayList<TestObj> list1 = new CopyOnWriteArrayList<>(list);

        /**启动一个线程随机删除数据*/
        new Thread(new ThreadClass(list1)).start();

        /** 遍历元素*/
        Iterator<TestObj> iterator = list1.iterator();
        while (iterator.hasNext()) {
            TestObj testObj = iterator.next();
        }

        System.out.println("end");
    }
}

这里需要注意的是需要先放入ArrayList中然后在通过CopyOnWriteArrayList构造函数中传入参数来实现这个测试。

如果直接将3.2中的实例代码中的ArrayList改为CopyOnWriteArrayList,程序会花大量的时间用在初始化上。因为CopyOnWriteArrayList每次修改数据都会复制一个新的数组。这种效率上的消耗也是比较大。

3.4 采用ConcurrentHashMap替换HashMap

如果上述例子中的ArrayList替换为HashMap,也会出现这个异常。解决方案是:多线程环境修改和遍历HashMap,采用ConcurrentHashMap替换HashMap类,可以避免出现ConcurrentModificationException异常。

示例代码:

package com.dashidan.faq4;

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 大屎蛋教程网-dashidan.com
 * 4.ConcurrentModifyException的产生原因及如何避免
 * Created by 大屎蛋 on 2018/5/24.
 */
public class Demo6 {
    public static void main(String[] args) {
        /** 初始化集合类*/
        ConcurrentHashMap<Integer, TestObj> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 1000000; i++) {
            map.put(i, new TestObj(i));
        }

        /**启动一个线程随机删除数据*/
        new Thread(new ThreadClass1(map)).start();

        /** 遍历元素*/
        Iterator<TestObj> iterator = map.values().iterator();
        while (iterator.hasNext()) {
            TestObj testObj = iterator.next();
        }

        System.out.println("end");
    }
}

class ThreadClass1 implements Runnable {

    Map<Integer, TestObj> map;

    public ThreadClass1(Map<Integer, TestObj> map) {
        this.map = map;
    }

    @Override
    public void run() {
        while (true) {
            if (map.size() > 0) {
                int index = new Random().nextInt(map.size());
                map.remove(index);
            }
        }
    }
}

4 其他方法

文中提到是作者常用的一些解决方案,还有其他的替代方案,比如采用同步锁等的方案等。在写代码的过程中多思考多总结。办法总比问题多。

转载请保留 原文链接.
1. 用户与权限管理模块 角色管理: 学生:查看实验室信息、预约设备、提交耗材申请、参与安全考核 教师:管理课题组预约、审批学生耗材申请、查看本课题组使用记录 管理员:设备全生命周期管理、审核预约、耗材采购与分发、安全检查 用户操作: 登录认证:统一身份认证(对接学号 / 工号系统,模拟实现),支持密码重置 信息管理:学生 / 教师维护个人信息(联系方式、所属院系),管理员管理所有用户 权限控制:不同角色仅可见对应功能(如学生不可删除设备信息) 2. 实验室与设备管理模块 实验室信息管理: 基础信息:实验室编号、名称、位置、容纳人数、开放时间、负责人 功能分类:按学科(计算机实验室 / 电子实验室 / 化学实验室)标记,关联可开展实验类型 状态展示:实时显示当前使用人数、设备运行状态(正常 / 故障) 设备管理: 设备档案:名称、型号、规格、购置日期、单价、生产厂家、存放位置、责任人 全生命周期管理: 入库登记:管理员录入新设备信息,生成唯一资产编号 维护记录:记录维修、校准、保养信息(时间、内容、执行人) 报废处理:登记报废原因、时间,更新设备状态为 "已报废" 设备查询:支持按名称、型号、状态多条件检索,显示设备当前可用情况 3. 预约与使用模块 预约管理: 预约规则:学生可预约未来 7 天内的设备 / 实验室,单次最长 4 小时(可设置) 预约流程:选择实验室→选择设备→选择时间段→提交申请(需填写实验目的) 审核机制:普通实验自动通过,高危实验(如化学实验)需教师审核 使用记录: 签到 / 签退:到达实验室后扫码签到,离开时签退,系统自动记录实际使用时长 使用登记:填写实验内容、设备运行情况(正常 / 异常),异常情况需详细描述 违规管理:迟到 15 分钟自动取消预约,多次违规限制预约权限 4. 耗材与安全管理模块 耗材管理: 耗材档案:名称、规格、数量、存放位置、
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值