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 语句,可以更灵活地处理程序中的错误情况。
80 1
|
2月前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
91 12
|
2月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
45 1
|
13天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
25 6
|
2月前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
2月前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
86 7
|
2月前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
97 6
|
11天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
13天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
13天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。