ConcurrentModificationException出现原因以及解决办法

ConcurrentModificationException出现的原因以及解决办法(JDK1.7)

ConcurrentModificationException 异常出现的原因

  • 首先看如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class T{
public static void main(string args[]){
List<String> list = new ArrayList<String>();
list.add("aa");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String temp = it.next();
if("aa".equals(temp)){
list.remove(temp);
}
}
}
}
异常结果如下:
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 Test.main(Test.java:11)

从异常信息来看,异常发生在ArrayList内部类的Itr的checkForComodification()方法中。

那我们从程序的 Iterator<String> it = list.iterator(); 开始,进入源码如下:

1
2
3
public Iterator<E> iterator(){
return new Itr();
}

从上面代码可以看出,返回的是一个Itr对象,再次寻找发现,Itr为ArrayList的内部类。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* An optimized version of AbstractList.Itr
*/

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();
}
}

首先解释几个变量的含义

cursor 游标,表示下一个要访问的元素索引

lastRet 表示上一个访问的元素索引

expectedModCount 表示对ArrayList修改次数的期望值,初始值等于modCount

modCount 是AbstractList的成员变量,表示对ArrayList修改的次数,在add以及remove、clear等方法中可以看出。初始值为0

还是看上面那个程序,当程序执行到while语句时,其实是执行Itr类的hasNext方法,根据此方法可知,cursor为0,size为1,返回true
注意此时 expectedModCount的值为0,接着执行next方法,返回第一个对象,即值为”aa”的字符串,此时cursor的值为1,执行if判断之
后执行remove方法,注意此时是ArrayList中的remove方法,下面看看remove方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
}

/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
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
}

从以上代码可知,最终执行的代码是fastRemove,执行完此方法之后,modCount的值为1,size的值为0
再次执行while的hasNext方法时,cursor的值为1,size的值为0,返回true,接着执行next方法,
此时,modCount=1,而expectedModCount的值为0,故抛出ConcurrentModificationException异常。

关键点是:remove方法修改了modCount的值,使得modCount与expectedModCount的值不一致引起的。

即使是for循环的形式也抛出此异常。

ConcurrentModificationException 异常的解决办法

既然知道了原因,那怎么解决呢?

细心的同学可能发现Itr类中也存在remove方法,此方法其实也是调用的list.remove方法,但是此方法多了一行代码

1
expectedModCount = modCount;

因此在迭代中删除元素时,使用Itr的remove方法。代码修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test{
public static void main(String args[]){
List<String> list = new ArrayList<String>();
list.add("aa");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String temp = it.next();
if("aa".equals(temp)){
it.remove();//注意此行代码
}
}
}
}

在单线程下,代码修改为如下代码没有问题,但是不适合多线程情况。多线程解决方案是使用CopyOnWrite来代替ArrayList

坚持原创技术分享,您的支持将鼓励我继续创作