集合遍历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 ...
随机推荐
- Excel的数据分析—排位与百分比
Excel的数据分析-排位与百分比 某班级期中考试进行后,按照要求仅公布成绩,但学生及家长要求知道排名.故欲公布成绩排名,学生可以通过成绩查询到自己的排名,并同时得到该成绩位于班级百分比排名(即该同学 ...
- 删除csdn自己上传的资源
原文地址:http://www.xuebuyuan.com/1875216.html 昨天晚上进行测试,上传了一个压缩包和大家分享,测试完成后,为了不想给被测试的公司造成伤害,决定把上传的包删除,结果 ...
- servlet的xml配置详解
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns ...
- 全面解析HTML5优缺点
HTML5是当下最主流的网页标准,它的出现给在线应用和手机游戏开发者带来了不少新机会.基于HTML5,开发者可以制作自己的网络游戏,而这个游戏无 论你在PC.手机还是平板电脑上,无论你用Chrome. ...
- vue echarts 给饼图中间添加文字 ,并且添加多个样式
最近根据设计要求写了一个统计图,以下是设计要求,要求中间文字分别是总数和汉字,样式分别不同 好吧具体的解决方案如下 方案一 series: [ { type:'pie', radius: ['50%' ...
- 如果json中的key需要首字母大写怎么解决?
一般我们命名都是驼峰式的,可是有时候和第三方接口打交道,也会遇到一些奇葩,比如首字母大写........额 这是个什么鬼,对方这么要求,那我们也得这么写呀. 于是乎,第一种方式:把类中的字段首字母大写 ...
- VBA提取HTML文件信息
Sub test() Dim html As Object, D As Object, W As Object, arr() Set html = CreateObject("m ...
- PHPSTORM 2016.2 注册
1.由于 http://idea.qinxi1992.cn/ OR http://us.idea.lanyus.com/ 都已经被禁掉了,所以就不能再用License server 去注册了. 如图所 ...
- vue组件参数校验
代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- Android开发 retrofit入门讲解
前言 retrofit基于okhttp封装的网络请求框架,网络请求的工作本质上是 OkHttp 完成,而 retrofit 仅负责网络请求接口的封装.如果你不了解OKhttp建议你还是先了解它在来学习 ...