一、问题发现
在迭代集合元素时,如果对集合做add/remove操作,会抛出java.util.ConcurrentModificationException异常。
如下代码所示:
package com.wbf.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
List<String> del = new ArrayList<String>();
del.add("5");
del.add("6");
del.add("7");
for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
String s = iter.next();
if (del.contains(s)) {
list.remove(s);
}
}
}
}
执行上诉代码,会抛出异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.wbf.list.ListDemo.main(ListDemo.java:23)
这是为什么呢?
二、问题分析
在集合中,如ArrayList,对它的做出修改操作(add/remove)时都会对modCount这个字段+1,modCount可以看作一个版本号,每次集合中的元素被修改后,都会+1(即使溢出)。接下来看看ArrayList从父类AbsrtactList中继承的iterator方法
public Iterator<E> iterator() {
return new Itr();
}
这个方法返回内部类Itr的实例对象,类Iter实现了接口Iterator,代码如下
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在内部类Itr中有一个属性expectedModCount,其默认的值等于modCount
int expectedModCount = modCount;
所以当我们调用集合类的iterator()方法时,返回迭代器对象时,expectedModCount被初始化为modCount
从内部类的代码中可以看出,它的remove()和next()方法中都调用了checkForComodification()方法,而这个方法做的事情就是判断modCount是否等于expectedModCount,如果不等于就抛出ConcurrentModificationException
在示例代码中,在进行集合迭代时,第一执行
String s = iter.next();
是不会有问题的,但是一旦满足条件,执行了
if (del.contains(s)) {
list.remove(s);
}
如果集合还有元素未迭代,再次执行
String s = iter.next();
时,就会抛出:java.util.ConcurrentModificationException异常
这个异常是执行Itr类的next()方法时调用checkForComodification()抛出的。之所以会抛异常是因为初始化的时候modCount是等于expectedModCount的,但是在执行了一次
if (del.contains(s)) {
list.remove(s);
}
后,modCount+1,在执行Itr类的next()方法时调用checkForComodification()发现modCount已经不等于expectedModCount了,所以抛出异常.
三、问题解决
1. 方法1
不要通过list.remove(s)来移除元素,而是iter.remove(),这样就没有了
为什么呢?看Itr类的remove()方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
从remove()方法的源码中,我们发现了十分重要的一行代码
expectedModCount = modCount;
一切都豁然开朗了吧。
2. 方法2
另外准备一个list用来保存需要移除的元素,在迭代完毕后一次性移除即可
package com.wbf.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
List<String> del = new ArrayList<String>();
del.add("5");
del.add("6");
del.add("7");
List<String> needDel = new ArrayList<String>();
for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
String s = iter.next();
if (del.contains(s)) {
//list.remove(s);
//iter.remove();
needDel.add(s);
}
}
list.removeAll(needDel);
}
}
这种方法就不用解释了吧。
四、问题补充
补充1:
在通过for(int i=0; i<list.size(); i++)方式遍历集合时,通过直接调用List类自身的remove()方法来移除元素,是完全没有问题的
package com.wbf.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
for (int i=0; i<list.size(); i++) {
list.remove(i);
}
}
}
理由很简单:虽然list.remove()会修改modCount的值,但是它没有调用checkForComodification()方法来校验modCount与expectedModCount是否相等,所以不会有java.util.ConcurrentModificationException异常的抛出
但是如果是通过foreach方式遍历集合的话,是会抛出java.util.ConcurrentModificationException异常的,代码如下:
package com.wbf.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
for (String s : list) {
list.remove(s);
}
}
}
因为foreach实现原理是转换为Iterator.next()来执行的。既然调用了Iterator的next()方法,必然需要通过checkForComodification()方法来校验modCount是否等于expectedModCount,此时由于list.remove(s)的原因,modCount与expectedModCount显然是不相等的,所以会抛出异常。
补充3:
其实从源码中可以看出,只有Itr类的remove()和next()方法在执行过程中调用了checkForComodification()来检查modCount是否等于expectedModCount,对于比如:get(),add()等方法都是没有做这项工作的,所以如果通过for(int i=0; i<list.size(); i++)遍历集合的时候,往集合中添加新的元素,是不会抛出ConcurrentModificationException异常的
package com.wbf.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
int count = 0;
for (int i=0; i<list.size(); i++) {
if (count < 3) {
list.add("count" + count++);
}
System.out.println(list.get(i));
}
}
}
执行完全没有问题。
但是这里必须将for(int i=0; i<list.size(); i++)与foreach区分开来
如果时foreach方式遍历集合时往集合中添加元素,执行时却会抛出java.util.ConcurrentModificationException异常,代码如下:
package com.wbf.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
int count = 0;
//for (int i=0; i<list.size(); i++) {
for (String s : list) {
if (count < 3) {
list.add("count" + count++);
}
System.out.println(s);
}
}
}
这是为啥呢?因为foreach实现原理是转换为Iterator.next()来执行的。既然调用了Iterator的next()方法,必然需要通过checkForComodification()方法来校验modCount是否等于expectedModCount,此时由于list.add("count" + count++)的原因,modCount与expectedModCount显然是不相等的,所以会抛出异常。
来源:oschina
链接:https://my.oschina.net/u/2290965/blog/706864