先来看出错代码:

/*需求:
遍历已有集合
如果在集合中发现存在字符串元素“world”
则在“world”后添加元素“javaee”
*/ List list = new ArrayList();
//多态的形式创建接口实现类对象
list.add("helllo");
list.add("java");
list.add("world"); //生成迭代器并判断有无world这个元素,如果有则向集合中添加“javaee”
Iterator it = list.iterator();
while(it.hasNext()){
String s = it.next();
if(s.equals("world")){
list.add("javaee");
}
} System.out.println(list);
//抛出异常:ConcurrentModificationException

这段代码中我试图在迭代的过程中通过list(List实现类对象)调用add方法向集合中添加元素并进行输出,但编译器在输出阶段抛出异常并终止了程序运行。

错误信息如下:

下面开始分析问题并找到解决方案:

1. 在错误信息中找到异常名称,将异常名称到帮助文档查看抛出异常的原因:

2. 通过报错信息定位抛出异常的方法。

错误信息中蓝色高亮的部分UseIterator.java:23表示的是抛出异常的方法所在行数,进一步跟进是ArrayList中的Itr内部类中调用的next方法出的问题,再进一步跟进是ArrayList中的next方法调用的checkForComodification抛出的异常。

3.源码分析(选中类名ctrl+b查看源码)

抛出异常的代码如下:

List<String> list = new ArrayList<String>();//创建集合对象
Iterator<String> it = list.iterator();//创建迭代器实现类对象 String s = it.next();//抛出异常的代码

从上面的代码中可以看到万恶之源是这个List,所以,先要有一个List,为了看起来更简洁这里我只拿了用到的内容过来,其他在这个案例中没有用到的就暂时没有管,下面的源码也会像这样简洁的拿过来看。

public interface List<E> extends Collection<E> {
Iterator<E> iterator();
boolean add(E e);
}

因为list是以多态的形式创建的ArrayList对象,所以也把ArrayList拿过来看看。

public class ArrayList<E> extends AbstractList<E> implements List<E>{
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
/*
expectedModCount:预期修改值
modCount:实际修改值(继承自父类AbstractList)
*/ @SuppressWarnings("unchecked")
//异常初步定位在内部类Itr的next方法中
public E next() {
checkForComodification();//异常根源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];
} //异常根源
final void checkForComodification() {
if (modCount != expectedModCount)
//当预期修改值与实际修改值不同抛出异常
throw new ConcurrentModificationException();
}
}
}

从定义中可以看到ArrayList继承了AbstractList<E>,Abstract也暂时先把定义拿过来,里面需要用到的内容后面进行完善。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>();

ArrayList的定义中可以看出,除了继承AbstractList<E>外,还实现了接口List<E>,所以ArrayList中应该要有add方法的实现,所以将add方法的实现也拿过来看看。

public class ArrayList<E> extends AbstractList<E> implements List<E>{
//重写接口中的add方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
} private class Itr implements Iterator<E> {
int expectedModCount = modCount;
/*
expectedModCount:预期修改值
modCount:实际修改值(继承自父类AbstractList)
*/ @SuppressWarnings("unchecked")
//异常初步定位在内部类Itr的next方法中
public E next() {
checkForComodification();
//异常根源checkForComodification()方法
...
} //异常根源
final void checkForComodification() {
if (modCount != expectedModCount)
//当预期修改值与实际修改值不同抛出该异常
throw new ConcurrentModificationException();
}
}
}

其实到这一步基本上已经能看出来个大概了,Itr实现了List中的Iterator接口,而出错的代码中的it正是Iterator的实现类对象,这里的Itr就是这个实现类。

Itr在实现next时调用了checkForComodification()方法将expectedModCount和modCount进行对比,如果两边不等就会抛出异常。那么现在要解决的就是expectedModCount和modCount是什么的问题了。

在Itr中有一个将modCount的值赋值给expectedModCount的操作,其中expectedModCount是itr中的成员变量,但是在ArrayList和Itr中都没有modCount的声明,那就只能是一种可能:modCount继承自父类。所以现在可以完善AbstractList<E>的一部分了

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{
protected transient int modCount = 0;
//实际修改值最初值为0
}

好了,到这里就破案了,最开始的时候expectedModCount和modCount的值都是0,但是在代码中我在迭代过程中通过list调用了一下add方法,但是在add方法中每add一次就会对modCount进行一次++,所以导致expectedModCount != modCount,最终抛出ConcurrentModificationException并发修改异常。

//ArrayList重写接口中的add方法
public boolean add(E e) {
modCount++;
//每次在集合中添加新的元素后实际修改值++
add(e, elementData, size);
return true;
}

既然现在找到了错误的源头,那就要想一个解决方案出来了,最终目的是查找是否有“world”这个元素,并向集合中添加元素。迭代器行不通就换一种方法进行迭代,例如for循环

for(int i = 0;i<list.size();++i){
String s = list.get(i);//通过get获取元素
if(s.equals("world")){
list.add("javaee");
}
}

但是这里又有一个问题,通过get获取元素为什么就不会报错了?空口无凭还是来看看代码吧,前面已经分析过的部分就不在赘述了,直接拿过来搞里头:

public interface List<E> extends Collection<E> {
boolean add(E e);
E get(int index);
}
public class ArrayList<E> extends AbstractList<E>implements List<E>{ //实现List接口中的get,add方法
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
} public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
}

源码说明一切,可以看到ArrayList实现get方法时没有比较expectedModCount和modCount的操作,所以不管add中对modCount的值++多少次都没有影响。

下面再捋一下思路。

4. 总结

  1. 编译器抛出运行时异常(ConcurrentModificationException)
  2. 追溯异常出现的方法(Itr.next().checkForComodification())
  3. 跟进代码找出问题根源。
  4. 找出解决方案:采用for循环调用list.get()的方式遍历集合则可以避开checkForComodification()中的if (modCount != expectedModCount)比较,此时再调用add向集合添加元素即可。

其中跟进代码思路如下:

  • 错误代码中通过list.iterator获取到元素迭代器,并通过循环调用next的方式实现遍历集合,当发现“world”时会执行add操作
  • ArrayList实现List中的add方法时加入了modCount++的操作,所以当向集合中添加元素后,modCount的值会发生变化
  • next方法调用了checkForComodification()方法,该方法中进行了if (modCount != expectedModCount)比较
  • 因为上一循环进行了add操作此时modCount值已经发生变化,再次进行比较时modCount != expectedModCount,所以抛出异常

大概就这些,欢迎指正。

【Java笔记】以并发修改异常为例总结的出错解决办法的更多相关文章

  1. java.util.ConcurrentModification并发修改异常

    在运行下面这段代码时出现了并发修改异常java.util.ConcurrentModification: public static void main(String[] args) { List l ...

  2. 集合并发修改异常-foreach的时候不可修改值

    直接上代码: 无意间发现的://这个方法本身是为后面的集合去掉前面集合的重复数据一直报错,并发修改异常,仔细看mainList正在迭代循环,然后我进行了remove操作,这个时候就会报这个错.故:总结 ...

  3. Java基础知识强化之集合框架笔记19:List集合迭代器使用之 并发修改异常的产生原因 以及 解决方案

    1. 我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. ConcurrentModi ...

  4. java 15 - 8 集合框架(并发修改异常的产生原因以及解决方案)

    问题?   我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现.  面试题: Concu ...

  5. 理解和解决Java并发修改异常ConcurrentModificationException(转载)

    原文地址:https://www.jianshu.com/p/f3f6b12330c1 理解和解决Java并发修改异常ConcurrentModificationException 不知读者在Java ...

  6. 理解和解决Java并发修改异常:ConcurrentModificationException

    參考文獻:https://www.jianshu.com/p/f3f6b12330c1 文獻来源:简书 关键字: Java Exception遇到异常信息Exception in thread &qu ...

  7. 集合框架之——迭代器并发修改异常ConcurrentModificationException

    问题: 我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. 使用普通迭代器出现的异常: ...

  8. 日历类和日期类转换 并发修改异常 泛型的好处 *各种排序 成员和局部变量 接口和抽象类 多态 new对象内存中的变化

    day07 ==和equals的区别? ==用于比较两个数值 或者地址值是否相同.  equals 用于比较两个对象的内容是否相同   String,StringBuffer.StringBuilde ...

  9. ArrayList在foreach删除倒数第二个元素不抛并发修改异常的问题

    平时我们使用ArrayList比较多,但是我们是否知道ArrayList在进行foreach的时候不能直接通过list的add或者move方法进行删除呢, 原因就是在我们进行foreach遍历的时候, ...

随机推荐

  1. 资源:docker-compose下载路径

    docker-compose下载路径: compose所有版本:https://github.com/docker/compose/releases

  2. Result Maps collection already contains value for cn.itcast.ssm.mapper.CompetesMapperCustom.baseMap

    在使用ssm时出现的错误: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. Cause: java.lang ...

  3. mybatis复杂映射

    1. 类型名对应 当实体类与表中字段完全一致时,mapper文件里返回类型用resultType,否则要用resultMap,并且建立resultMap映射 package com.rf.domain ...

  4. (精)题解 guP4878 [USACO05DEC] 布局

    差分约束模版题 不过后三个点简直是满满的恶意qwq 这里不说做题思路(毕竟纯模板),只说几个坑点: 1. 相邻的两头牛间必须建边(这点好像luogu没有体现),例如一组数据: 4 1 1 1 4 10 ...

  5. python pil 图像加工处理

    from PIL import Imagefrom PIL import ImageEnhanceim=Image.open("d://aa.jpg","r") ...

  6. scrapy设置自己的headers referer字段

    1.在middlewares中添加自己的新类: class Mylei(object): def process_request(self,request,spider): referer=reque ...

  7. java 实现中英文拼写检查和错误纠正?可我只会写 CRUD 啊!

    简单的需求 临近下班,小明忙完了今天的任务,正准备下班回家. 一条消息闪烁了起来. "最近发现公众号的拼写检查功能不错,帮助用户发现错别字,体验不错.给我们系统也做一个." 看着这 ...

  8. windows下搭建vue开发环境实践

    Vue.js是一套构建用户界面的 "渐进式框架".与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计.Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已 ...

  9. 解决 Github 打不开或打开很慢的问题

    解决 Github 打不开或打开很慢的问题 方法一 一.确定 github 网站的 ip 打开网址:http://github.com.ipaddress.com/ 192.30.253.112 gi ...

  10. linux下快速安装pyenv管理多版本python

    起因 一直服务器python项目都是放docker跑,这次为了测试,不得不在宿主机跑,就必须安装python3.7,但是ubuntu16.04下有点麻烦 尝试 源码安装,懒,算了,也不想污染服务器环境 ...