java.util.ConcurrentModificationException异常分析
Java在操作ArrayList、HashMap、TreeMap等容器类时,遇到了java.util.ConcurrentModificationException异常。以ArrayList为例,如下面的代码片段:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; public class Test { public static void main(String[] args){
List<String> strList = new ArrayList<String>();
strList.add("string1");
strList.add("string2");
strList.add("string3");
strList.add("string4");
strList.add("string5");
strList.add("string6"); // 操作方式1:while(Iterator);报错
Iterator<String> it = strList.iterator();
while(it.hasNext()) {
String s = it.next();
if("string2".equals(s)) {
strList.remove(s);
}
} // 解决方案1:使用Iterator的remove方法删除元素
// 操作方式1:while(Iterator):不报错
// Iterator<String> it = strList.iterator();
// while(it.hasNext()) {
// String s = it.next();
// if("string2".equals(s)) {
// it.remove();
// }
// } // 操作方式2:foreach(Iterator);报错
// for(String s : strList) {
// if("string2".equals(s)) {
// strList.remove(s);
// }
// } // 解决方案2:不使用Iterator遍历,注意索引的一致性
// 操作方式3:for(非Iterator);不报错;注意修改索引
// for(int i=0; i<strList.size(); i++) {
// String s = strList.get(i);
// if("string2".equals(s)) {
// strList.remove(s);
// strList.remove(i);
// i--;// 元素位置发生变化,修改i
// }
// } // 解决方案3:新建一个临时列表,暂存要删除的元素,最后一起删除
// List<String> templist = new ArrayList<String>();
// for (String s : strList) {
// if(s.equals("string2")) {
// templist.add(s);
// }
// }
// // 查看removeAll源码,其使用Iterator进行遍历
// strList.removeAll(templist); // 解决方案4:使用线程安全CopyOnWriteArrayList进行删除操作
// List<String> strList = new CopyOnWriteArrayList<String>();
// strList.add("string1");
// strList.add("string2");
// strList.add("string3");
// strList.add("string4");
// strList.add("string5");
// strList.add("string6");
// Iterator<String> it = strList.iterator();
// while (it.hasNext()) {
// String s = it.next();
// if (s.equals("string2")) {
// strList.remove(s);
// }
// } }
}
执行上述代码后,报错如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at concurrentModificationException.Test.main(Test.java:21)
在第21行报错,即it.next(),迭代器在获取下一个元素时报错。找到java.util.ArrayList第830行,看到it.next()的源代码,如下:
@SuppressWarnings("unchecked")
public E next() {
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];
}
调用了checkForComodification()方法,代码如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
即,比较modCount和expectedModCount两个是否相等。modCount是ArrayList从AbstractList继承来的属性,查看modCount属性的doc文档,可知,modCount表示列表(list)被结构性修改(structurally modified)的次数。structurally modified是指造成列表中元素个数发生变化的操作,ArrayList中的add,addAll,remove, fastRemove,clear, removeRange, ensureCapacity, ensureCapacityInternal, ensureExplicitCapacity等方法都会使modCount加1,而batchRemove,removeAll,retainAll等方法则根据删除的元素数增加modCount的值(removeAll和retainAll都是调用batchRemove实现,具体modCount的修改算法还需研究)。
第一个代码片段中的操作方式1和操作方式2都是采用了迭代器的方式。当使用iterator方法得到Iterator对象(或者使用listIterator获得ListItr对象),其实是返回了一个Iterator接口的实现类ArrayList$Itr(继承自AbstractList$Itr),该类为ArrayList的一内部类,该类中有一个expectedModCount字段,当调用ArrayList$Itr的next方法时,会先检查modCount的值是否等于expectedModCount的值(其实在调用next, remove, previous, set, add等方法时都会检查),不相等时就会抛出java.util.ConcurrentModificationException异常。这种现象在java doc中称作fail-fast。
为什么会抛出该异常呢?从代码可以看到调用了6次add方法,这时modCount的值也就为6,当当使用iterator方法得到Iterator对象时把modCount的值赋给了expectedModCount,开始时expectedModCount与modCount是相等的,当迭代到第二个元素(index=1)“string2”时,因为if条件为true,于是又调用了remove方法,调用remove方法时modCount值又加1,此时modCount的值为7了,而expectedModCount的值并没有改变,当再次调用ArrayList$Itr的next方法时检测到modeCount与expectedModCount不相等了,于是抛出异常。
当把if语句写成if(s.equals("string5"))时又没有抛出该异常,这又是为什么呢?ArrayList$Itr中还有一个名为cursor的字段用来指向迭代时要操作的元素索引,初始值为0,每调用一次next方法该字段值加1,注意是先从集合中取出了元素再加1的。当判断"string5"时,注意是倒数第二个元素,这些cursor的值为5,移除掉元素"string5"时,List的size为5,当调用ArrayList$Itr的hasNext方法判断有无下一个元素时,判断的依据为cursor的值与size是否相等,不相等则还有下一个元素,而此时两者值刚好相等,也就没有往下执行next方法了,也就没有抛出异常,因此删掉倒数第二个元素时不会抛异常的异常。
解决方案有四种,直接看第一段代码即可。
参考链接:
http://blog.csdn.net/xtayfjpk/article/details/8451178
《多线程情况下只能使用CopyOnWriteArrayList》http://www.2cto.com/kf/201403/286536.html
java.util.ConcurrentModificationException异常分析的更多相关文章
- java.util.ConcurrentModificationException 异常问题详解
环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...
- java.util.ConcurrentModificationException异常原因及解决方法
在java语言中,ArrayList是一个很常用的类,在编程中经常要对ArrayList进行删除操作,在使用remove方法对ArrayList进行删除操作时,报java.util.Concurren ...
- java集合--java.util.ConcurrentModificationException异常
ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...
- Java处理java.util.ConcurrentModificationException异常
代码: public static void reduce(HashMap<String, Integer> hashMap, final Integer count) { Iterato ...
- java.util.ConcurrentModificationException异常排查
java.util.ConcurrentModificationException对于这个异常我们一般会认为是在遍历list的时候对这个list做了add,remove等修改操作造成的,最近在线上 ...
- java.util.ConcurrentModificationException 异常解决的方法及原理
近期在修程序的bug,发现后台抛出下面异常: Exception in thread "main" java.util.ConcurrentModificationExceptio ...
- java.util.ConcurrentModificationException异常的解决
问题复现: List<String> list = new ArrayList<>();list.add("11");list.add("55&q ...
- java.util.ConcurrentModificationException异常;java.util.ConcurrentModificationException实战
写代码遇到这个问题,很多博客文章都是在反复的强调理论,而没有对应的实例,所以这里从实例出发,后研究理论: 一.错误产生情况 1 .字符型 (1)添加 public static void main(S ...
- java.util.ConcurrentModificationException 异常原因和解决方法
不要在 foreach 循环里进行元素的 remove/add 操作.remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁. 注意: 1.foreach遍历 ...
随机推荐
- Chrome插件(Extensions)开发实践
内容摘自:http://www.cnblogs.com/mfryf/p/3701801.html
- hdu 3682 10 杭州 现场 C To Be an Dream Architect 容斥 难度:0
C - To Be an Dream Architect Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d &a ...
- pycharm 教程(一)安装和首次使用
PyCharm 是我用过的Python编辑器中,比较顺手的一个.而且可以跨平台,在 mac 和 windows 下面都可以用,这点比较好. 首先预览一下 PyCharm 在实际应用中的界面:(更改了P ...
- JS之Iterations
for in.for of.for each in 1.for in:用于遍历数组或者对象的属性(对数组或者对象的属性进行循环操作),for ... in 循环中的代码每执行一次,就会对数组的元素或者 ...
- java中break,continue,标签实现goto效果(编程思想)
goto 编程语言中一开始就有goto关键词了.事实上,goto起源于汇编语言的程序控制:“若条件A成立,则调到这里:否则跳到那里”. goto语句时在源码级别上的跳转,这导致了其不好的名誉.于是go ...
- OMAP4之DSP核(Tesla)软件开发学习(二)Linux内核驱动支持OMAP4 DSP核
注:必须是Linux/arm 3.0以上内核才支持RPMSG,在此使用的是.config - Linux/arm 3.0.31 Kernel Configuration.(soure code fro ...
- secureCRT不能输入
用secureCRT建了一个串口COM1后,连接上开发板后,可以正确接受和显示串口的输出,但是按键输入无效. 解决: Session Options -> Connection -> Se ...
- python3文件操作方法
在python3中,我们可以使用open打开一个文件,那么打开文件后,文件有什么操作方法呢?接下来我就记录一下比较常用的方法. 1. close() 关闭打开的文件 2. fileno() 返回文件句 ...
- 414 - Machined Surfaces
Sample Input (character "B" for ease of reading. The actual input file will use the ASCII- ...
- ballerina 学习二十五 项目docker 部署&& 运行
ballerina 官方提供了docker 的runtime,还是比较方便的 基本项目创建 使用cli创建项目 按照提示操作就行 ballerina init -i 项目结构 添加了dockerfil ...