集合遍历remove时ConcurrentModificationException异常
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
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异常的更多相关文章
- 在多线程的情况下是由Iterator遍历修改集合对象,报ConcurrentModificationException()异常的根因分析
遍历List时抛ConcurrentModificationException异常原理分析 http://www.blogjava.net/houlinyan/archive/2008/04/ ...
- java集合遍历删除指定元素异常分析总结
在使用集合的过程中,我们经常会有遍历集合元素,删除指定的元素的需求,而对于这种需求我们往往使用会犯些小错误,导致程序抛异常或者与预期结果不对,本人很早之前就遇到过这个坑,当时没注意总结,结果前段时间又 ...
- Python list遍历remove()时的一个小BUG
有这样一个列表: s=list('abcdefg') 现在因为某种原因我们需要从s中踢出一些不需要的元素,方便起见这里直接以踢出所有元素的循环代替: for e in s: s.remove(e) 结 ...
- List remove及ConcurrentModificationException异常
参考:http://blog.csdn.net/androidboy365/article/details/50540202/ 解决方案 // 1 使用Iterator提供的remove方法,用于删除 ...
- Java 遍历集合时产生的ConcurrentModificationException异常
前几天做Java实验的时候,打算用foreach遍历一个ArrayList集合,并且当集合中的某个元素符合某个值时删除这个元素.写完运行时抛出了ConcurrentModificationExcept ...
- java集合--java.util.ConcurrentModificationException异常
ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...
- - 集合 遍历 foreach Iterator 并发修改 ConcurrentModificationException MD
目录 目录 为什么不能在 foreach 循环里进行元素的 remove/add 操作 背景 foreach 循环 问题重现 fail-fast remove/add 做了什么 正确姿势 直接使用普通 ...
- java list集合遍历时删除元素
转: java list集合遍历时删除元素 大家可能都遇到过,在vector或arraylist的迭代遍历过程中同时进行修改,会抛出异常java.util.ConcurrentModification ...
- 迭代(遍历)时候不可以使用集合的remove和add方法,但可使用Java迭代器的remove和add方法
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator 方式. 反例: public class ForeachTest { private ...
随机推荐
- Delphi常用字符串函数
Delphi常用字符串函数 一.字符转换函数1.ord(input[i])返回字符表达式 input 左端起第 I 字符的ASCII 码值.2.CHAR()将ASCII 码转换为字符.如果没有输入 ...
- LED 发光二极管压降
常用发光二极管的压降 1. 直插超亮发光二极管压降 主要有三种颜色,然而三种发光二极管的压降都不相同,具体压降参考值如下: 红色发光二极管的压降为2.0--2.2V 黄色发光二极管的压降为1.8—2 ...
- Spring Boot 启动,1 秒搞定!
Java技术栈 www.javastack.cn 优秀的Java技术公众号 原文: dev.to 翻译: ImportNew.com - 唐尤华译文: http://www.importnew.com ...
- spring data jpa使用 (转:http://www.manongjc.com/article/25284.html#four_1_7)
Jap相关的使用 时间:2018-12-18 本文章向大家介绍Jap相关的使用,主要包括Jap相关的使用使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下. ...
- upper_bound() lower_bound() 用法
头文件 #include<algorithm> #include<iostream>#include<vector>#include<algorithm> ...
- 使用CSS3开启GPU硬件加速提升网站动画渲染性能
遇到的问题: 网站本身设计初衷就没有打算支持IE8及以下版本浏览器,并不是因为代码兼容性问题,而是真的不想迁就那些懒得更新自己操作系统和浏览器的用户,毕竟是我自己的网站,所以我说了算!哈哈~ 没有了低 ...
- fastjson转jackson
使用fastjson有个内存oom的问题,我们应该尽量使用jackjson,为什么呢?因为fastjson会引发一个oom,很潜在的危险,虽然jackjson的api真的非常好用,对于解析json串来 ...
- hive sparksession查询只显示defalt库问题
1.spark环境记得拷贝进hive.xml 2.SparkSession.builder().enableHiveSupport()记得加上enableHiveSupport 3.window记得w ...
- boost相关函数
1.boost::scoped_ptr是一个比较简单的智能指针,它能保证在离开作用域之后它所管理对象能被自动释放 #include <iostream> #include <boos ...
- 火狐浏览器缓存导致JS已经改变的ID没改变
问题主要就是火狐浏览器缓存. 比如,自己写一个JS,如下: $(document).ready(function () { $("#bigRoom").live("cli ...