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!!!

相关文章
|
8天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
8天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
|
9天前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
|
11天前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
|
13天前
|
Java
Java异常捕捉处理和错误处理
Java异常捕捉处理和错误处理
14 1
|
5天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
13天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
4天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
3天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
下一篇
无影云桌面