Java中的ConcurrentModificationException异常原因分析及解决办法1/2/3/4

简介: Java中的ConcurrentModificationException异常原因分析及解决办法1/2/3/4

0.需求

从List列表中删除删除一个元素

#JDK版本
java version "1.8.0_241"

1.现象

public class ListTest {

    List<String> list = new ArrayList<>();

    @Before
    public void before(){
        //初始化数据
        for (int i = 0; i < 6; i++) {
            list.add(String.valueOf(i));
        }
        //打印
        list.forEach(t->{
            System.out.println("val:" + t);
        });

    }

    @Test
    public void remove(){
        //删除元素"2"
        list.forEach(t -> {
            if("2".equals(t)){
                list.remove(t);
            }
        });
        //打印
        list.forEach(t->{
            System.out.println(t);
        });
    }
 }

运行后,发生如下异常:

java.util.ConcurrentModificationException
    at java.util.ArrayList.forEach(ArrayList.java:1260)
    at cn.pbdata.issue.manual.simple.ListTest.remove(ListTest.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

从异常信息可以发现,异常发生在java.util.ArrayList.forEach(ArrayList.java:1260)方法中。

查看ArrayList的源码:

@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    //第1260行
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

其中,
modCount是ArrayList中的一个成员变量。从其中的注释说明中可以看到modCount表示对List的修改次数,每次调用add()方法或者remove()方法,就会对modCount进行加1操作。

在此例中,list最初有6个元素,那么最初的modCount=6,

我们进行了remove一次,所以 modCount=7,

而expectedModCount还是为6,导致modCount != expectedModCount,所以就出现了并发异常。

解决方案

如果要在List中删除元素,可以有以下几种办法:

1. 使用迭代器

我们重构之前的测试代码,使用 Iterator进行删除操作。在 Iterator迭代器中,可以使用remove()。测试代码如下:

    @Test
    public void removeV2(){
        //删除元素"2"
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String t = iterator.next();
            if("2".equals(t)){
                iterator.remove();
            }
        }

        //打印
        list.forEach(t->{
            System.out.println(t);
        });
    }

测试通过,运行正常。

这是因为迭代器在循环过程中调用是安全的,remove()方法就不会导致ConcurrentModificationException。

2. 迭代期间不删除

如果一定要使用for-each循环,那么我们可以再构建一个list,保存需要删除的元素,等到迭代结束后,再移除元素。测试代码如下:

    @Test
    public void removeV3(){
        //删除元素"2"
        List<String> toDelList = new ArrayList();

        list.forEach(t -> {
            if("2".equals(t)){
                toDelList.add(t);
            }
        });

        list.removeAll(toDelList);

        //打印
        list.forEach(t->{
            System.out.println(t);
        });
    }

这也是解决问题的一种方法。

3. 使用removeIf()

从Java 8开始,Collection接口引入了removeIf()方法。我们可以使用函数式编程的方法进行处理。测试代码如下:

    @Test
    public void removeV4(){
        //删除元素"2"
        list.removeIf(t -> "2".equals(t));

        //打印
        list.forEach(t->{
            System.out.println(t);
        });
    }

4. 使用函数式编程

还可以使用函数式编程中的流处理进行删除。测试代码如下:

    @Test
    public void removeV5(){
        //删除元素"2"
        List<String> afterList = list.stream()
                                        .filter(t -> (!"2".equals(t)))
                                        .map(Object::toString)
                                        .collect(Collectors.toList());

        //打印
        afterList.forEach(t->{
            System.out.println(t);
        });
    }

end!!!

相关文章
|
2月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
92 1
|
2月前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
130 12
|
2月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
49 1
|
2天前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
25 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
2天前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
19 5
|
2天前
|
人工智能 自然语言处理 搜索推荐
【潜意识Java】了解并详细分析Java与AIGC的结合应用和使用方式
本文介绍了如何将Java与AIGC(人工智能生成内容)技术结合,实现智能文本生成。
20 5
|
2天前
|
SQL Java 数据库连接
【潜意识Java】Java中JDBC过时方法的替代方案以及JDBC为什么过时详细分析
本文介绍了JDBC中一些常见过时方法及其替代方案。
21 5
|
2天前
|
Java 数据库连接 数据库
【潜意识Java】深度分析黑马项目《苍穹外卖》在Java学习中的重要性
《苍穹外卖》项目对Java学习至关重要。它涵盖了用户管理、商品查询、订单处理等模块,涉及Spring Boot、MyBatis、Redis等技术栈。
22 4
|
2天前
|
Java 数据库连接 数据库
【潜意识Java】使用 Ruoyi 框架开发企业级应用,从零开始的实践指南和分析问题
本文介绍了基于Spring Boot的开源企业级框架Ruoyi,涵盖环境搭建、项目初始化及用户管理模块的创建。
25 4
|
2天前
|
SQL Java API