ConcurrentModificationException 并发修改异常剖析及解决方案
最近在回头打基础学习Java SE,研究List集合的过程中,遇到了ConcurrentModificationException 并发修改异常。在此记录下遇到问题的原因解析和解决方案。错误千奇百怪,解决问题的方法还是通用的,所以也算是记录下遇到问题该怎么自己去利用代码提示寻找原因的方法叭~
问题复现
最开始的需求是我需要遍历集合,并在指定位置添加元素
上来先丢出来错误示例代码:
package Gather;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*** @author rob* @title: ConcurrentModificationExceptionDemo* @projectName java-learn* @description: ConcurrentModificationException 并发修改异常* @date 2021/11/1下午2:36*/
public class ConcurrentModificationExceptionDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("hello");list.add("world");list.add("java");Iterator<String> it = list.iterator();while (it.hasNext()) {String s = it.next();if (s.equals("world")) {list.add("javaee");}}System.out.println(list);}
}
然后我们再来看一下报错提示内容
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)at Gather.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:24)
报错解析
步入正题,我们先从最下面的报错提示开始看:
at Gather.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:24)
报错的是程序源代码的第24行,即:
String s = it.next();
就说明后面的next()
方法有问题,它不对劲(因为显然前面的创建String对象不可能报错呐 ),再看错误提示的上一行:
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
说明程序还调用了java.util.ArrayList
的子类Itr的next()
方法(对的哦,$就是子类的意思 ),然后再往上看错误提示:
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
说明程序还调用了java.util.ArrayList
的子类Itr的checkForComodification()
方法
好的,看完错误提示了,从上面的分析可以知道,错误的重点在next()
和checkForComodification()
方法上,接下来再回到程序中去找源码:
我们有一个List对象,且调用了add()
和iterator()
方法,所以我们先去java.util.List
去找这两个方法的源码( 如果你用的是IDEA等高级IDE,按住Ctrl再单击你需要查看的代码如现在的List
就可以自动跳转至源码页了,没有就看我放出来的叭~ ):
public interface List<E> {boolean add(E var1);Iterator<E> iterator();
}
发现这是一个接口,并没有实现具体的方法,所以我们再看程序源码 :
List<String> list = new ArrayList<>();
该List对象是通过ArrayList<>()
方法利用多态性new出来的,所以我们又需要去java.util.ArrayList
下去翻源码,由于它实现了List接口,所以需要找到复写的add()
和iterator()
方法,同时发现在iterator()
方法中返回了子类Itr
对象,从错误提示中得到这里只提取我们上面所提及到的next()
和checkForComodification()
方法(别的没用到所以就不用看了):
public class ArrayList<E> extends AbstractList<E> implements List<E> {public boolean add(E e) {++this.modCount;this.add(e, this.elementData, this.size);return true;}public Iterator<E> iterator() {return new ArrayList.Itr();}private class Itr implements Iterator<E> {int expectedModCount;Itr() {this.expectedModCount = ArrayList.this.modCount;}public E next() {this.checkForComodification();int i = this.cursor;if (i >= ArrayList.this.size) {throw new NoSuchElementException();} else {Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();} else {this.cursor = i + 1;return elementData[this.lastRet = i];}}}final void checkForComodification() {if (ArrayList.this.modCount != this.expectedModCount) {throw new ConcurrentModificationException();}}}
}
诶嘿,是不是看到了熟悉的ConcurrentModificationException?我们最终实在next()
方法出的异常,所以看Itr.next()
,第一行就调用了checkForComodification()
,而它只有一个if语句,所以报错抛出异常的一定是它。
什么时候才会抛出这个异常呢?if语句的条件是ArrayList.this.modCount != this.expectedModCount
,那么modCount和expectedModCount是什么呢,具体数值又是怎么来的,就是解决这个报错的关键了。
modCount:是实际修改集合的次数
expectedModCount:程序预期的修改集合的次数
从Itr的无参构造函数中我们可以看到:
Itr() {this.expectedModCount = ArrayList.this.modCount;}
啊哈,最开始他们是一致的,所以是我们在中间那一步操作导致的不一致引起的抛出ConcurrentModificationException 并发修改异常。
那modCount
这个变量来自哪里呢?
通过查看源码(就是在这个变量上按住Ctrl单击呐 ),得知它是来自ArrayList继承的父类AbstractList里:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {protected transient int modCount = 0;
}
所以一开始,这两个值都是0
那问题就是哪一步使得modCount
改变了。查看程序源代码得知当程序出现"world"
时就会执行add()
方法:
public boolean add(E e) {++this.modCount;this.add(e, this.elementData, this.size);return true;}
++this.modCount;
使用前modCount
+1,所以成功调用add()
方法前:
modCount
=1;expectedModCount
=0;
1=0?不等于呀,所以程序肯定要抛出异常
原因
在通过迭代器遍历的时候,通过集合添加了元素,造成迭代器判断预期修改的次数和实际修改的次数不一致,这样就造成了并发修改异常。
解决方案
既然迭代器会出现问题那么用另一种循环不就好了,例如for循环格式:
for (int i = 0; i < list.size(); i++) {String s = list.get(i);if (s.equals("world")) {list.add("JavaEE");}}
修改后完整的代码为:
package Gather;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*** @author rob* @title: ConcurrentModificationExceptionDemo* @projectName java-learn* @description: ConcurrentModificationException 并发修改异常* @date 2021/11/1下午2:36*/
public class ConcurrentModificationExceptionDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("hello");list.add("world");list.add("java");for (int i = 0; i < list.size(); i++) {String s = list.get(i);if (s.equals("world")) {list.add("JavaEE");}}System.out.println(list);}
}
成功运行并在控制台答应输出结果为:
[hello, world, java, JavaEE]
这是因为修改后for循环遍历程序虽然也修改了modCount
,但是遍历的时候使用的是ArrayList.get()
方法,该方法仅通过索引获取并返回对应元素,并没有执行checkForComodification()
方法,也就不会进行判断:
public E get(int index) {Objects.checkIndex(index, this.size);return this.elementData(index);}
好啦好啦,溜了溜了
ConcurrentModificationException 并发修改异常剖析及解决方案相关推荐
- ConcurrentModificationException并发修改异常
A:ConcurrentModificationException出现: /*Iterator it = list.iterator(); //获取迭代器while(it.hasNext()) { / ...
- php 迭代器迭代中文时重复,3种方式解决iterator迭代器并发修改异常
3种方式解决iterator迭代器并发修改异常ConcurrentModificationException 在使用迭代器的时候,时长会遇到 ConcurrentModificationExcepti ...
- java迭代器删除元素出异常_java迭代器移除元素出现并发修改异常的原因及解决...
迭代器(Iterator的对象)主要用于遍历集合,体现的就是迭代器模式. Iterator接口定义了以下四种方法. boolean hasNext():如果集合还没遍历完就返回true. Object ...
- 第三次学JAVA再学不好就吃翔(part79)--并发修改异常产生的原因及解决方案
学习笔记,仅供参考,有错必纠 文章目录 集合 并发修改异常产生的原因及解决方案 ConcurrentModificationException 解决方案 集合 并发修改异常产生的原因及解决方案 在这个 ...
- 理解和解决Java并发修改异常ConcurrentModificationException(转载)
原文地址:https://www.jianshu.com/p/f3f6b12330c1 理解和解决Java并发修改异常ConcurrentModificationException 不知读者在Java ...
- java 并发修改_理解和解决Java并发修改异常ConcurrentModificationException
your name.jpg 关键字: Java Exception 不知读者在Java开发的过程中有没有遇到类似的异常信息 Exception in thread "main" j ...
- Java并发--ConcurrentModificationException(并发修改异常)异常原因和解决方法
Java并发--ConcurrentModificationException(并发修改异常)异常原因和解决方法 参考文章: (1)Java并发--ConcurrentModificationExce ...
- Java中遍历集合的并发修改异常解决方案
遍历集合的删除元素的问题 问题引出: 当我们遍历集合从中找出某一批元素并删除的时候, 可能出现一种并发修改异常问题. 哪些变量会存在这个问题? 迭代器遍历集合且直接用集合删除元素的时候可能出现, 例如 ...
- List遍历过程中并发修改异常解决方案
错误示例 使用foreach:失败 ArrayList<String> list = new ArrayList<>(); list.add("111"); ...
最新文章
- R语言使用caret包中的createMultiFolds函数对机器学习数据集进行交叉验证抽样、返回的样本列表长度为k×times个、times为组内抽样次数
- Silverlight 参考:三维效果(透视转换) -- MSN
- 智能合约重构社会契约(11)天德区块链智能合约系统
- To B设计系统 - 在平平淡淡中开花结果
- linux 如何让.开头的文件不隐藏_每日一课 | Linux:如何gzip文件夹
- WEBApp-搭建Android开发环境
- 使用C#体验函数式编程之——Partial application(局部应用)
- 【DP】【高精】幸运票 (jzoj 2122)
- 力扣617. 合并二叉树(JavaScript)
- js中对datagrid ,repeater的checkbox进行全选反选
- php debug pit,start.php
- 黄聪:微信h5支付demo微信H5支付demo非微信浏览器支付demo微信wap支付
- 计算机基础视频教程百度云,计算机应用基础视频教程
- 好用的工程项目管理软件推荐
- 不动产租赁运营平台,为不动产租赁提供强劲的运营支持
- 【剑指offer】JZ55 二叉树的深度 python
- 干货分享 | 创业公司绝对不会告诉你他们在用的工具们
- 计算机系统期末考试感想
- 结构体内数组arr[0]或者arr[1]变量的作用及使用方法
- selenium 警告框处理
热门文章
- matlab可靠性优化,MATLAB在机械可靠性优化设计中的应用.pdf
- 基于simulink的无刷直流电动机性能仿真
- hbase java编程,HBase编程实例
- linux 格斗游戏,新闻|“战斗砖块剧场”占据了 Steam 上 Linux游戏排行榜首位
- 第八场多校联盟 Problem A: 序号互换 【模拟】
- Visual Studio Code安装(软件及插件)教程
- 伙伴云CEO戴志康:Discuz !之后,打造数字化武器,助力元气森林们做爆品
- 基于android手机的备份与还原
- sqltrace相关汇总
- 怎么sketch画板导出html,sketch符号和导出画板 – Sketch入门UI设计教程