Java在操作ArrayList、HashMap、TreeMap等容器类时,遇到了java.util.ConcurrentModificationException异常。以ArrayList为例,如下面的代码片段:
[java] view plaincopy
- 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);
- // }
- // }
- }
- }
执行上述代码后,报错如下:
[plain] view plaincopy
- <span style="font-size:14px;">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)
- </span>
在第21行报错,即it.next(),迭代器在获取下一个元素时报错。找到java.util.ArrayList第830行,看到it.next()的源代码,如下:
[java] view plaincopy
- <span style="font-size:14px;"> @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];
- }</span>
调用了checkForComodification()方法,代码如下:
[java] view plaincopy
- <span style="font-size:14px;"> final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }</span>
即,比较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异常。
为什么会抛出该异常呢?从代码可以看到调用了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方法了,也就没有抛出异常,因此删掉倒数
第二个元素时不会抛异常的异常。
解决方案有四种,直接看第一段代码即可。