一:什么时候出现?

  当我们用迭代器循环list的时候,在其中用list的方法新增/删除元素,就会出现这个错误。

package com.sinitek.aml;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; public class ConcurrentModificationExceptionDemo { public static void main(String[] args) { List<String> arrayList = new ArrayList<>(); arrayList.add("a");
arrayList.add("b");
arrayList.add("c"); Iterator<String> iterator = arrayList.iterator();
while(iterator.hasNext()){
String tmp = iterator.next();
if ("a".equals(tmp)){
arrayList.remove(tmp); }
} }
}

二:为什么会出这个错误? 

 如上图所示,运行代码就会有这个错误,我们看一下堆栈。

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.sinitek.aml.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:22)

  

  可以看到,是在ArrayList里面的Itr内部类里的checkForModification()方法里,抛出的这个异常,我们看下源码。

    final void checkForComodification() {
//如果修改的次数 和 预期修改的次数不一致,就会报错
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

  可以看到,上面两个关键的值是:modCount和expectModCount,其中的modCount来自ArrayList的父类,AbstractList,有兴趣的可以看下上面的翻译,也说明了这个字段的作用,正是为了fail-fast(快速失败)设计的。

  该值会在new出对象的时候,初始化为0;并且在每次的修改/删除操作时自增。

  

//arrayList的add方法,会先调用一次检查容量是否够的操作,我们可以看到,jdk专门在里面加了注释,会 Increments modCount, 增加modCount的值
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} //确保新增元素容量大小够用的方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //首先先自增一次 // overflow-conscious code
if (minCapacity - elementData.length > 0) //判断最小需要的容量是否大于数组的长度(这里有个疑问,为什么一定相减然后判断是否>0,直接比较会有溢出的问题需要考虑么)
grow(minCapacity); //扩容
} //删除方法,也会有modCount++的操作
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 work return oldValue;
}

  

那我们看一下,expectedModCount时怎么变化的。

  在ArrayList的内部类Itr(这个命名是不是有点随意了)里面,会看到expectedModCount是作为成员变量一起初始化的,所以我们在最开始的时候,调用list.iterator()方法的时候,就会初始化expectedModCount,将他变成和modCount一样的值。所以问题出在:

    1. new ArrayList,新增后,modCount会有一个初始值0。

    2. 调用iterator()方法后,会把expectedModCount的值初始化为modCount的值,此时,这两个值是一致的。

    3. 如果在循环里,使用list对象新增/删除对象(本文最上面所作的操作),modCount就会自增变化,而此时,expectedModCount却不会改变。

    4. 调用iterator里面的next() -> checkForComodification(),判断modCount==expectModCount的时候,就会发现不一致,从而抛出这个异常。

三:怎么解决。

  有两种方法。

    • 使用Itr类中的remove方法移除,但是如果用的是从Iterable继承的iterator返回的Iterator是没有add()方法的接口的,如果需要add(),那么需要调用list专有的迭代器listInterator()方法返回的ListIterator().
    • 不适用迭代器,使用原生的循环方式,这样就可以避免这个异常。(增强for只是一个语法糖(Syntactic Sugar)本质上还是使用了迭代器,也会有这个异常)。

四:为什么要定义这个异常?

    凡事多问一个为什么,jdk为什么要怎么设计,很简单,其实从命名上就能看出来,当我们在循环的时候,无论是我们在迭代循环的时候(目前我们就是这种情况),或者是其他的线程修改了这个(这个应该是jdk主要想解决的),都会出现数据不一致的情况,所以需要这个异常,让你fail-fast,早点异常早点再次尝试等。

  Q:为什么不在add()方法的时候,同时修改expectModCount,这样就他们一致了不就行了?

  A:add()方法在调用的时候,有可能没有这个Itr这个对象,而且单单修改值是没有用的,需要把对应的迭代器内部保存数据的值也对应的修改,只修改expectModCount有点类似掩耳盗铃。

  Q:那jdk可以新建一个重载方法,让itr对象当作参数,把他传进来,修改对应的保存数据的数组和对应的expectModCount,这样就能保证两个都一致了把。

  A:确实,这种方法对于上文这种fail-fast确实适用,但是还有一种情况就是:可能修改list的线程和迭代的线程都不是同一个,所以根本就没有itr这个对象,也就无法修改这个值了。

ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。的更多相关文章

  1. ConcurrentModificationException并发修改异常

    //创建集合对象 Collection c = new ArrayList(); c.add("hello"); c.add("world"); c.add(& ...

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

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

  3. 29.2 Iterator 迭代器ConcurrentModificationException:并发修改异常处理

    /** Iterator:迭代器* * 需求:判断集合中是否包含元素java,如果有则添加元素android * Exception in thread "main" java.u ...

  4. 【Java笔记】以并发修改异常为例总结的出错解决办法

    先来看出错代码: /*需求: 遍历已有集合 如果在集合中发现存在字符串元素"world" 则在"world"后添加元素"javaee" */ ...

  5. List集合遍历时修改元素出现并发修改异常总结

    什么是并发修改异常: 当我们在遍历实现了collection接口与iterator接口的集合时(List.Set.Map), 我们可以通过遍历索引也可以通过迭代器进行遍历.在我们使用迭代器进行遍历集合 ...

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

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

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

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

  8. ConcurrentModificationException(并发修改异常)的解决

    [异常解释] ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.[产生的原因] 迭代器是依赖于集合而存在的,在判断成功后,集合 ...

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

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

  10. 大杂烩 -- Iterator 并发修改异常ConcurrentModificationException

    基础大杂烩 -- 目录 大杂烩 -- Java中Iterator的fast-fail分析 大杂烩 -- Iterator 和 Iterable 区别和联系 问题: 在集合中,判断里面有没有" ...

随机推荐

  1. 不建议升级windows11的理由

    此文写于2022年9月13日,用于警告那些蠢蠢欲动想升级win11的伙伴. win11发布后,最亮眼的功能当然是自带"安卓模拟器",但作为一名程序开发者的我其实是不愿意马上尝鲜的, ...

  2. 重复造轮子 SimpleMapper

    接手的项目还在用 TinyMapper 的一个早期版本用来做自动映射工具,TinyMapper 虽然速度快,但在配置里不能转换类型,比如 deleted 在数据库中用 0.1 表示,转换成实体模型时没 ...

  3. 怎样编写正确、高效的 Dockerfile

    基础镜像 FROM 基础镜像 基础镜像的选择非常关键: 如果关注的是镜像的安全和大小,那么一般会选择 Alpine: 如果关注的是应用的运行稳定性,那么可能会选择 Ubuntu.Debian.Cent ...

  4. 使用logstash读取MySQL数据传输到es,并且@timestamp字段采用MySQL中的字段时间--建议采用这个

    MySQL中数据样式 ES中数据样式 input { jdbc { jdbc_connection_string => "jdbc:mysql://192.168.0.145:3306 ...

  5. flask中验证用户登录的装饰器

    from flask import Flask,render_template,redirect,request,session from functools import wraps app = F ...

  6. Kafka QuickStart

    环境版本 操作系统:CentOS release 6.6 (Final) java版本: jdk1.8 kafka 版本: kafka_2.11-1.1.1.tgz 安装kafka 1. 下载压缩包, ...

  7. loam详细代码解析与公式推导

    loam详细代码解析与公式推导(基础理论知识) 一.基础坐标变换 loam中欧拉角解算都采用R P Y 的解算方式,即先左乘R, 再左乘P, 最后左乘Y,用矩阵表示为: R = Ry * Rp * R ...

  8. Mybatis PageHelper 使用的注意事项

    什么时候会导致不安全的分页? PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的. 只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查 ...

  9. Linux Block模块之IO合并代码解析

    1 IO路径 从内核角度看,进程产生的IO路径主要有三条: 缓存IO:系统绝大部分IO走的这种形式,充分利用文件系统层的page cache所带来的优势.应用程序产生的IO经系统调用落入page ca ...

  10. KTV和泛型(2)

    很多使用泛型的小伙伴,都会有一个疑惑:为什么有的方法返回值前带<T>.<K, V>之类的标记,而有的方法返回值前又什么都不带呢?就像这样: // 实体基类 class Enti ...