java.util.ConcurrentModificationException异常出现的原因及解决方法

上一篇博客:

 写在前面:大家好!我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭!
用知识改变命运,用知识成就未来!加油 (ง •̀o•́)ง (ง •̀o•́)ง

文章目录

  • 前言
  • for-each循环具体逻辑
  • 快速失败迭代器
  • 抛出该异常的情况
  • 解决方案
    • 使用迭代器的remove()方法
    • 普通for循环遍历
    • 避免在迭代过程中直接修改集合

前言

 今天在写代码的时候碰到了异常 java.util.ConcurrentModificationException 出现这个异常的情况是使用增强for循环遍历集合,在遍历集合的时候修改集合(删除集合中的某个元素)。类似下面的代码:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class ListTest {public static void main(String[] args) {List<String> nameList = new ArrayList<>();nameList.add("Ye Wenjie");nameList.add("Luo Ji");nameList.add("Cheng Xin");nameList.add("Wall-breaker");nameList.add("Thomas Wade");nameList.add("Wang Miao");for (String name : nameList) {// 判断是否符合剔除条件if ("Wall-breaker".equals(name)) {nameList.remove(name);}}System.out.println(nameList);}
}

执行代码之后抛出异常:

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at ListTest.main(ListTest.java:15)

 通过查看异常的堆栈跟踪信息及相关代码可以发现是因为使用 增强for循环 遍历列表时会调用 checkForComodification() 方法检查当前列表是否有并发修改,如果有则会抛出 java.util.ConcurrentModificationException 异常。

for-each循环具体逻辑

 当遍历列表时调用了 ArrayList 类中的内部迭代器类 Itr,该类实现了 Iterator<E> 接口。具体是实现逻辑是什么呢?for-each循环 是如何调用该类中的方法的呢?我们可以通过 javap 命令反编译 .class 文件后可以看到如下信息(为了方便查看去除了不重要的信息):

Compiled from "ListTest.java"
public class ListTest {public ListTest();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: new           #2                  // class java/util/ArrayList3: dup4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V7: astore_18: aload_19~60: 字符串创建及初始化操作......61: pop62: aload_163: invokeinterface #11,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;68: astore_269: aload_270: invokeinterface #12,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z75: ifeq          10878: aload_279: invokeinterface #13,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;84: checkcast     #14                 // class java/lang/String87: astore_3108: getstatic     #17                 // Field java/lang/System.out:Ljava/io/PrintStream;111: aload_1112: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V115: return
}

 通过反编译代码的 63~84 可以发现首先调用了 List 接口的 iterator() 方法。这个方法返回一个迭代器对象,用于遍历列表。然后调用迭代器对象的 hasNext() 方法,检查是否还有下一个元素。如果有下一个元素则调用迭代器对象的 next() 方法,获取下一个元素;通过该逻辑实现循环遍历集合。可以发现该逻辑与我们使用 迭代器 进行循环遍历的逻辑是一样的,等价于下面这段利用迭代器遍历的代码:

// 使用迭代器遍历列表
Iterator<String> iterator = nameList.iterator();
while (iterator.hasNext()) {String name = iterator.next();// 判断是否符合剔除条件if ("Wall-breaker".equals(name)) {iterator.remove(); // 使用迭代器的remove方法移除元素}
}

 通过查看源码可以发现在调用迭代器对象的 next() 方法时调用的checkForComodification() 方法进行检查,具体代码如下:

private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);final int size = ArrayList.this.size;int i = cursor;if (i >= size) {return;}final Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}while (i != size && modCount == expectedModCount) {consumer.accept((E) elementData[i++]);}// update once at end of iteration to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

checkForComodification() 方法会判断 modCount 是否与 expectedModCount 相等。modCount 是指 Listnew 开始被修改的次数,当调用 remove()、add()、addAll() 等方法时该值会增加;expectedModCount 是指 Iterator 期望这个 list 被修改的次数是多少次。该值是在 Iterator 初始化的时候将 modCount 的值赋给了expectedModCount

 所以当我们在调用 增强for循环 的同时在循环内部调用 remove() 方法会导致这两个值不相等因此会抛出 ConcurrentModificationException() 异常。for-each循环实际上使用的是 快速失败迭代器,它不允许在迭代过程中修改集合。

快速失败迭代器

 快速失败(fail-fast)行为在 Java 集合框架中是一种常见的行为模式,它具有以下好处:

  1. 检测并发修改:快速失败行为有助于检测在迭代过程中对集合的非预期结构性修改。这通常是由于集合被多个线程并发修改,或者在迭代过程中修改了集合,导致的并发修改异常。
  2. 提高安全性:通过抛出ConcurrentModificationException异常,快速失败行为可以防止程序在未检测到错误的情况下继续执行,从而避免可能的数据不一致性和不可预测的行为。
  3. 避免非确定性行为:如果没有快速失败机制,迭代器可能会在检测到并发修改时继续执行,导致不确定的结果。快速失败机制确保了一旦检测到修改,立即停止迭代,避免了非确定性行为。
  4. 调试和错误定位:当抛出ConcurrentModificationException异常时,开发者可以更容易地定位和理解代码中的错误,因为异常提供了一个明确的信号,表明代码中存在并发修改的问题。
  5. 保护数据完整性:快速失败机制可以防止在数据结构被修改时继续执行可能破坏数据完整性的操作。
  6. 代码清晰和维护性:使用快速失败迭代器的代码通常更易于理解和维护,因为它们明确地表明了不允许在迭代过程中修改集合。
  7. 避免隐藏的错误:如果没有快速失败机制,迭代器可能会在不抛出异常的情况下继续执行,这可能会导致更难以发现的错误,比如覆盖数据或者丢失更新。

抛出该异常的情况

 通过搜索该异常可以发现 Java 中抛出该异常的情况通常包括以下几种:

  1. 在迭代过程中直接修改集合:在使用 for-each循环迭代器 遍历集合时,如果直接通过集合对象调用 add()、remove() 等方法修改集合,可能会抛出此异常。

  2. 多线程并发修改:当一个线程在迭代集合的同时,另一个线程修改了集合的结构(如添加或删除元素)。

  3. 集合被多个线程同时修改,没有适当的同步机制:在多线程环境中,如果没有适当的同步控制,不同的线程可能会同时修改同一个集合,导致异常。

解决方案

 通过以上描述我们可以发现该异常通常出现在集合在迭代过程中被直接修改或在多线程并发时同一个集合被不同的线程同时修改。解决方式主要有以下几种:

使用迭代器的remove()方法

 在迭代过程中,如果需要删除元素,应该使用迭代器的 remove() 方法,而不是直接通过集合对象调用 remove()。通过以上描述我们可以了解到增强for循环实际上使用的是迭代器,我们如果想在遍历的同时修改元素列表可以将遍历方式改为迭代器遍历,使用迭代器的 remove() 方法。这是因为在调用迭代器的 remove() 方法后紧接着对 expectedModCount 进行了同步。移除源码如下:

public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}// ArrayList.this.remove(lastRet);源码
public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}

 因为迭代器在调用移除方法时进行了同步,所以就不会因 expectedModCount != modCount 抛出 java.util.ConcurrentModificationException 异常。

普通for循环遍历

 可以使用普通 for 循环遍历集合并在循环中删除元素**(不推荐该方式)**。使用该方法要注意手动调整索引,因为不手动调整会出现跳过元素的情况。如果使用正序遍历可以只在不删除元素时才增加索引,代码如下:

for (int i = 0; i < nameList.size(); ) {if ("Cheng Xin".equals(nameList.get(i))) {nameList.remove(i);} else {i++; // 只有当不删除当前元素时,索引才增加}
}

也可以直接使用逆序循环,这样就不用手动调整索引,因为删除之后不会导致列表元素前移,代码如下:

for (int i = nameList.size() - 1; i >= 0; i--) {if ("Cheng Xin".equals(nameList.get(i))) {nameList.remove(i);}
}

避免在迭代过程中直接修改集合

 其实最好是避免在迭代过程中直接修改集合。我们完全可以通过更好的设计避免在迭代的过程中修改集合。在迭代前或迭代后修改集合,或者收集需要修改的元素,迭代完成后统一处理更安全。在迭代过程中修改集合很容易出现不易发现的业务逻辑错误(例如忘记手动管理索引导致跳过元素)或者不可预测的行为,一旦出现比较难发现。因此我也修改了设计避免了在循环中修改集合。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/890394.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构---------二叉树前序遍历中序遍历后序遍历

以下是用C语言实现二叉树的前序遍历、中序遍历和后序遍历的代码示例&#xff0c;包括递归和非递归&#xff08;借助栈实现&#xff09;两种方式&#xff1a; 1. 二叉树节点结构体定义 #include <stdio.h> #include <stdlib.h>// 二叉树节点结构体 typedef struct…

设计模式の命令访问者迭代器模式

文章目录 前言一、命令模式二、访问者模式三、迭代器模式 前言 本篇是关于设计模式中命令模式、访问者模式、以及迭代器模式的学习笔记。 一、命令模式 命令模式是一种行为型设计模式&#xff0c;其核心目的在于将命令的发送者和接受者解耦&#xff0c;提供一个中间层对命令进行…

UE5仿漫威争锋灵蝶冲刺技能

这两天玩了一下漫威争锋Marvel Rivals&#xff0c;发现是UE5做的&#xff0c;对里面一些角色技能挺感兴趣的&#xff0c;想简单复刻一下技能功能&#xff0c;顺便复习一下学过的知识 首先把摄像机设置调整一下 CameraBoom里搜索lag 把摄像机延迟关掉 &#xff0c;这样摄像机就…

常用类晨考day15

1.基本数据类型以及对应包装类 Byte Short Integer Long Float Double Boolean Character 2.什么是自动拆箱和装箱&#xff0c;jdk版本有什么要求&#xff1f;代码举 例并标明 Integer a 100; // 装箱 int b a; // 拆箱 从JDK1.5才开始支持 3.NumberFormatException是什么异常…

Vue中<script setup></script>的主要语法元素和特性

<script setup>是 Vue 3 中引入的一种新的组件内脚本语法糖&#xff0c;它带来了更简洁、高效的组件逻辑编写方式。 以下是 <script setup> 的主要语法元素和特性&#xff1a; 1.导入和使用 直接在 <script setup> 中导入依赖&#xff0c;不需要在 compon…

深度学习推理速度优化指南

深度学习推理速度优化指南 简介一、显卡频率设置二、查看当前显卡频率三、调整显卡频率范围注意事项总结 简介 本文旨在探讨深度学习推理过程中 CUDA 或 TensorRT 推理速度变慢的问题&#xff0c;并提供实用的解决策略。我们将从显卡频率设置、模型权重优化等方面出发&#xf…

etcd+京东hotkey探测使用

qhotKey链接 京东hotkey把热点数据默认缓存在了本地缓存caffeine中&#xff0c;也可以存到redis中&#xff0c;但是京东hotkey的SDK没有redis的实现方法&#xff0c;因此需要自己实现。 官方目录结构下&#xff1a;分别是client客户端&#xff08;要打包引入到自己的项目&…

如何实现层叠布局

文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了GirdView Widget,本章回中将介绍Stack这种Widget,闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在Flutter中Stack主要用来叠加显示其它的Widget,类似我们日常生活中的楼层或者说PS中的图层,因此它也是一…

Java 上机实践11(组件及事件处理)

&#xff08;大家好&#xff0c;今天分享的是Java的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 Plug&#xff1a;程序实现 方法一&#xff08;记事本&#xff09; 方法二&#xff08;IDEA&#xff09; 实验一&am…

UVM 验证方法学之interface学习系列文章(十一)virtual interface 再续篇

一 前言 并非总是可以将被测单元(DUT)视为一个黑盒,即仅监控和驱动DUT的顶层端口信号。这一点在从模块级测试转向更大规模的系统级测试时尤为明显。有时,我们需要了解实现细节以便访问DUT内部的信号。这被称为白盒验证。 Verilog一直提供从其他作用域访问几乎任何层次结构…

本地如何启动casdoor

1、下载代码 GitHub - casdoor/casdoor at v1.777.0 下载对应tag的代码&#xff0c;我这里选择的时v1.777.0版本 通过网盘分享的文件&#xff1a;casdoor-1.777.0.zip 链接: https://pan.baidu.com/s/1fPNqyJYeyfZnem_LtEc0hw 提取码: avpd 2、启动后端 1、使用goland编译…

CSDN外链失效3:

参考我之前的博客&#xff1a; 外链失效博客1&#xff1a;随想笔记1&#xff1a;CSDN写博客经常崩溃&#xff0c;遇到外链图片转存失败怎么办_csdn外链图片转存失败-CSDN博客 外链失效博客2&#xff1a;网络随想2&#xff1a;转语雀_md格式转语雀lake格式-CSDN博客 markdown…

Kubernates

kubernates是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes的目标是让部署容器化的应用简单并且高效&#xff08;powerful&#xff09;,Kubernetes提供了应用部署&#xff0c;规划&#xff0c;更新&#xff0c;维护的一种机制。 架构…

Pycharm 更改字体大小

更改代码字体的大小 更改软件字体的大小

Ubuntu20.04解决docker安装后is the docker daemon running? 问题

Ubuntu20.04解决docker安装后is the docker daemon running? 问题 问题描述问题分析问题解决 问题描述 docker info后报错 ERROR: Cannot connect to the Docker daemon at unix:///root/.docker/desktop/docker.sock. Is the docker daemon running? errors pretty printi…

STM32完全学习——CRC校验

一、STM32F407CRC校验的补充 由于STM32F407的CRC校验&#xff0c;并没有什么配置的选项&#xff0c;就会导致他只能进行32位&#xff0c;且初始值是0XFFFFFFFF&#xff0c;输入和输出都没有反转的CRC校验。为了弥补这些不足点&#xff0c;于是自己编写函数来增加这些功能 //输…

CSS系列(27)- 图形与滤镜详解

前端技术探索系列&#xff1a;CSS 图形与滤镜详解 &#x1f3a8; 致读者&#xff1a;探索CSS的艺术表现力 &#x1f44b; 前端开发者们&#xff0c; 今天我们将深入探讨 CSS 图形和滤镜效果&#xff0c;学习如何创建引人注目的视觉效果。 基础图形 &#x1f680; 几何形状…

四、使用langchain搭建RAG:金融问答机器人--构建web应用,问答链,带记忆功能

经过前面3节完成金融问答机器人基本流程&#xff0c;这章将使用Gradio构建web应用&#xff0c;同时加入memory令提示模板带有记忆的&#xff0c;使用LCEL构建问答链。 加载向量数据库 from langchain.vectorstores import Chroma from langchain_huggingface import HuggingF…

矩阵链乘法【东北大学oj数据结构10-2】C++

矩阵链乘法问题的目标是找到最有效的方法来乘以给定的 n 个矩阵 M1,M2,M3,...,Mn。 编写一个程序&#xff0c;读取 Mi 的维度&#xff0c;并找到最小标量乘法以计算最大链链乘法 M1M2...Mn。 输入 在第一行中&#xff0c;给出了一个整数 n。 在接下来的 n 行中&#xff0c;矩阵…

深度学习之超分辨率算法——SRCNN

网络为基础卷积层 tensorflow 1.14 scipy 1.2.1 numpy 1.16 大概意思就是针对数据&#xff0c;我们先把图片按缩小因子照整数倍进行缩减为小图片&#xff0c;再针对小图片进行插值算法&#xff0c;获得还原后的低分辨率的图片作为标签。 main.py 配置文件 from model im…