【Java笔记】以并发修改异常为例总结的出错解决办法
先来看出错代码:
/*需求:
遍历已有集合
如果在集合中发现存在字符串元素“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. 总结
- 编译器抛出运行时异常(ConcurrentModificationException)
- 追溯异常出现的方法(Itr.next().checkForComodification())
- 跟进代码找出问题根源。
- 找出解决方案:采用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笔记】以并发修改异常为例总结的出错解决办法的更多相关文章
- java.util.ConcurrentModification并发修改异常
在运行下面这段代码时出现了并发修改异常java.util.ConcurrentModification: public static void main(String[] args) { List l ...
- 集合并发修改异常-foreach的时候不可修改值
直接上代码: 无意间发现的://这个方法本身是为后面的集合去掉前面集合的重复数据一直报错,并发修改异常,仔细看mainList正在迭代循环,然后我进行了remove操作,这个时候就会报这个错.故:总结 ...
- Java基础知识强化之集合框架笔记19:List集合迭代器使用之 并发修改异常的产生原因 以及 解决方案
1. 我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. ConcurrentModi ...
- java 15 - 8 集合框架(并发修改异常的产生原因以及解决方案)
问题? 我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. 面试题: Concu ...
- 理解和解决Java并发修改异常ConcurrentModificationException(转载)
原文地址:https://www.jianshu.com/p/f3f6b12330c1 理解和解决Java并发修改异常ConcurrentModificationException 不知读者在Java ...
- 理解和解决Java并发修改异常:ConcurrentModificationException
參考文獻:https://www.jianshu.com/p/f3f6b12330c1 文獻来源:简书 关键字: Java Exception遇到异常信息Exception in thread &qu ...
- 集合框架之——迭代器并发修改异常ConcurrentModificationException
问题: 我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. 使用普通迭代器出现的异常: ...
- 日历类和日期类转换 并发修改异常 泛型的好处 *各种排序 成员和局部变量 接口和抽象类 多态 new对象内存中的变化
day07 ==和equals的区别? ==用于比较两个数值 或者地址值是否相同. equals 用于比较两个对象的内容是否相同 String,StringBuffer.StringBuilde ...
- ArrayList在foreach删除倒数第二个元素不抛并发修改异常的问题
平时我们使用ArrayList比较多,但是我们是否知道ArrayList在进行foreach的时候不能直接通过list的add或者move方法进行删除呢, 原因就是在我们进行foreach遍历的时候, ...
随机推荐
- 18 shell 重定向以及文件描述符
1.对重定向的理解 2.硬件设备和文件描述符 文件描述符到底是什么 3.Linux Shell 输出重定向 4.Linux Shell 输入重定向 5.结合Linux文件描述符谈重定向 6.Shell ...
- Python中调用Linux命令并获取返回值
方法一.使用os模块的system方法:os.system(cmd),其返回值是shell指令运行后返回的状态码,int类型,0表示shell指令成功执行,256/512表示未找到,该方法适用于she ...
- 63. Unique Paths II 动态规划
description: https://leetcode.com/problems/unique-paths/ 机器人从一堆方格的左上角走到右下角,只能往右或者往下走 ,问有几种走法,这个加了难度, ...
- 盘点linux操作系统中的10条性能调优命令,一文搞懂Linux系统调优
原文链接:猛戳这里 性能调优一直是运维工程师最重要的工作之一,如果您所在的生产环境中遇到了系统响应速度慢,硬盘IO吞吐量异常,数据处理速度低于预期值的情况,又或者如CPU.内存.硬盘.网络等系统资源长 ...
- python 16篇 多线程和多进程
1.概念 线程.进程 进程 一个程序,它是一组资源的集合 一个进程里面默认是有一个线程的,主线程 多进程是可以利用多核cpu的线程 最小的执行单位 线程和线程之间是互相独立的 主线程等待子线程执行结束 ...
- vivo x9i ADB 模拟点击
手机连接电脑无反应,安装360驱动大师 更多设置--关于---多次点击软件版本号--开启开发者选项 USB调试--USB模拟点击(需要密码开启)
- Java基础00-运算符4
1. 算术运算符 1.1 运算符和表达式 1.2 算数运算符 余数的计算取余数是指整数除法中被除数未被除尽部分,且余数的取值范围为0到除数之间(不包括除数)的整数 ,例如27除以6,商数为4,余数为3 ...
- hapv-一个可以播放,下载国内主流视频的播放器
electron 开发的一个可以播放,下载国内主流视频的播放器.A player developed by electron that can play and download domestic m ...
- Day10 类与对象-面向对象编程(1)
面向对象编程(OOP) 面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据. 抽象 三大特征: 封装 继承 多态 从认识论角度考虑是先有对象后有类.对象,是具体的事物.类,是抽象的, ...
- React事件绑定的方式
一.是什么 在react应用中,事件名都是用小驼峰格式进行书写,例如onclick要改写成onClick 最简单的事件绑定如下: class ShowAlert extends React.Compo ...