1.集合遍历时候,有时候需要remove或add操作,这时候遍历方式可能会影响程序运行

  例如:

 @Test
public void test1() {
List<Integer> intList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
intList.add(Integer.valueOf(i));
} // 迭代器遍历, 异常
Iterator<Integer> iterator_int = intList.iterator();
while (iterator_int.hasNext()) {
Integer integer = iterator_int.next(); //ConcurrentModificationException
if (integer.intValue() == 5) { //这里选择集合靠中间的数据操作
//intList.remove(integer); //这里使用集合的remove方法
intList.add(55);
}
} // foreach遍历, 异常
for (Integer value : intList) { //ConcurrentModificationException
if (value.intValue() == 5) {
intList.remove(value); //这里使用集合的remove方法
}
} //普通for循环 , 正常
for (int i = 0; i < intList.size(); i++) {
if (intList.get(i) == 5) {
intList.remove(intList.get(i));
}
} }

2.为什么上面的迭代器和foreach遍历会有异常?

首先,看迭代器方式遍历,在 iterator_int.next()  方法出报异常.看一下源码:

① 在父类AbstractList中定义了一个int型的属性:modCount

  protected transient int modCount = 0;

② 在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。

注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。

③AbstractList中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,生成一个Itr对象(Iterator接口)返回:

public Iterator iterator() { return new Itr(); }

④ Itr实现了Iterator()接口,其中也定义了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。

int expectedModCount = modCount;

注:内部成员类Itr也是ArrayList类的一个成员,它可以访问所有的AbstractList的属性和方法。理解了这一点,Itr类的实现就容易理解了。

⑤ checkForComodification (检查是否并发修改)

  /**
  * 在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作
  * 这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。
  * 在AbstractList中,使用了一个简单的机制来规避这些风险。 
  * 这就是modCount和expectedModCount的作用所在
  */

断点可以看到,remove操作后,modeCount=21, 而expectedModCount=20 不相等,抛出异常.

下面是集合类的remove方法:

public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
fastRemove方法:
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++; //remove会加1,所以checkForComodification校验时候会异常
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
}

3.foreach遍历是对迭代器封装了一下,所以也会报异常. 

4. 普通for循环不会异常,因为使用的checkForComodification是另一个内部类的方法

SubList :

 

checkForComodification方法:

 

 5.fail-fast机制

有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。
线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,
同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。 线程A继续遍历执行next方法时,
通告checkForComodification方法发现expectedModCount = N , 而modCount = N + 1,两者不等,
这时就抛出ConcurrentModificationException 异常,从而产生fail-fast机制。

6.避免fail-fast机制

① 方法1

在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法。看看ArrayList中迭代器的remove方法的源码:
 public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
SubList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

可以看到,该remove方法并不会修改modCount的值,并且不会对后面的遍历造成影响,

因为该方法remove不能指定元素,只能remove当前遍历过的那个元素,所以调用该方法并不会发生fail-fast现象。该方法有局限性。

例:

 // intList.remove(integer);  //集合类remove
iterator_int.remove(); //迭代器的remove()方法

方法2

使用java并发包(java.util.concurrent)中的类来代替ArrayList 和hashMap。

比如使用 CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList几乎一样,CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。

该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,

所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

对于HashMap,可以使用ConcurrentHashMap,ConcurrentHashMap采用了锁机制,是线程安全的。

在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,

取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。

即迭代不会发生fail-fast,但不保证获取的是最新的数据。

 

--------------------- 
参考:https://blog.csdn.net/zymx14/article/details/78394464

https://blog.csdn.net/weixin_40254498/article/details/81386920

集合遍历remove时ConcurrentModificationException异常的更多相关文章

  1. 在多线程的情况下是由Iterator遍历修改集合对象,报ConcurrentModificationException()异常的根因分析

    遍历List时抛ConcurrentModificationException异常原理分析     http://www.blogjava.net/houlinyan/archive/2008/04/ ...

  2. java集合遍历删除指定元素异常分析总结

    在使用集合的过程中,我们经常会有遍历集合元素,删除指定的元素的需求,而对于这种需求我们往往使用会犯些小错误,导致程序抛异常或者与预期结果不对,本人很早之前就遇到过这个坑,当时没注意总结,结果前段时间又 ...

  3. Python list遍历remove()时的一个小BUG

    有这样一个列表: s=list('abcdefg') 现在因为某种原因我们需要从s中踢出一些不需要的元素,方便起见这里直接以踢出所有元素的循环代替: for e in s: s.remove(e) 结 ...

  4. List remove及ConcurrentModificationException异常

    参考:http://blog.csdn.net/androidboy365/article/details/50540202/ 解决方案 // 1 使用Iterator提供的remove方法,用于删除 ...

  5. Java 遍历集合时产生的ConcurrentModificationException异常

    前几天做Java实验的时候,打算用foreach遍历一个ArrayList集合,并且当集合中的某个元素符合某个值时删除这个元素.写完运行时抛出了ConcurrentModificationExcept ...

  6. java集合--java.util.ConcurrentModificationException异常

    ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...

  7. - 集合 遍历 foreach Iterator 并发修改 ConcurrentModificationException MD

    目录 目录 为什么不能在 foreach 循环里进行元素的 remove/add 操作 背景 foreach 循环 问题重现 fail-fast remove/add 做了什么 正确姿势 直接使用普通 ...

  8. java list集合遍历时删除元素

    转: java list集合遍历时删除元素 大家可能都遇到过,在vector或arraylist的迭代遍历过程中同时进行修改,会抛出异常java.util.ConcurrentModification ...

  9. 迭代(遍历)时候不可以使用集合的remove和add方法,但可使用Java迭代器的remove和add方法

    不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式. 反例: public class ForeachTest { private ...

随机推荐

  1. 解决方案-CRM:Vtiger CRM

    ylbtech-解决方案-CRM:Vtiger CRM vtiger CRM是一套基于Web以销售能力自动化(SFA)为主的客户关系管理系统(CRM).它基于是SugarCRM专业版(SPL1.1.2 ...

  2. Java设计模式(一)外观模式(门面模式)- 结构型模式

    模式的定义 门面模式(Facade Pattern)也叫做外观模式,是一种比较常用的封装模式,其定义如下:要求一个子系统的外部与其内部通信必须通过一个统一的对象进行.门面模式提供一个高层次的接口,使得 ...

  3. DRF的JWT用户认证

    目录 DRF的JWT用户认证 JWT的认证规则 JWT的格式 JWT认证的流程 JWT模块的导入为 JWT的使用 DRF的JWT用户认证 从根本上来说,JWT是一种开放的标准(RFC 7519), 全 ...

  4. python执行环境

    转自 http://www.cnblogs.com/BeginMan/p/3191856.html 一.python特定的执行环境 在当前脚本继续进行 创建和管理子进程 执行外部命令或程序 执行需要输 ...

  5. Jmeter----请求依赖之边界值提取器

    填写左边界和右边界 引用变量名就是要存储的变量名词

  6. pop&dismiss

    //dismiss到根视图 - (void)dismissToRootViewController{ UIViewController *vc = self; while (vc.presenting ...

  7. mysql连接超时的问题处理

    1. 内网 ts 连接mysql 有时候会连接失败, 原因是 连接超时, 当时所有服务器一起启动,抢占资源,导致连接超过10s. 现在增加一次连接机会, 增加一些日志. 2. 并且对mysql 全局参 ...

  8. 钉钉小程序----使用阿里的F2图表

    在钉钉小程序中使用F2的图表遇见很多问题 不能点击或者点击错乱的问题还没有解决,因为我解决不了........................... ------------------------- ...

  9. ZuulFilter

    在服务网关中定义过滤器,只需要继承ZuulFilter抽象类,实现其定义的四个抽象函数,就可对请求进行拦截与过滤 过滤器两个功能: 路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入 ...

  10. Windows copy

    将一份或多份文件复制到另一个位置. COPY [/D] [/V] [/N] [/Y | /-Y] [/Z] [/L] [/A | /B ] source [/A | /B]     [+ source ...