之前没有关注过这个,直到有天类似这样的代码上线:
public class ForEachTest {
public static void main(String[] args){
List<String> test = new ArrayList<String>();
test.add("a");
test.add("b");
test.add("c");
test.forEach(a ->{
if(a.equals("a")){
test.remove(a);
}
});
}
}
导致出现如下异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList.forEach(ArrayList.java:1258)
at ForEachTest.main(ForEachTest.java:14)
为什么会报错呢?让我们通过源码一探究竟。
由于我的反编译工具,没法反编译lamda表达式,所以我把上述foreach代码调整为:
for(String a:test){
if(a.equals("a")){
test.remove(a);
}
}
异常堆栈为:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
at java.util.ArrayList$Itr.next(ArrayList.java:857)
at ForEachTest.main(ForEachTest.java:14)
结果反编译出来和我代码是一样的。不知道别人文章里面是怎么反编译出来的。
直接把别人反编译的结果拿来:
List a = new ArrayList();
a.add("1");
a.add("2");
Iterator i$ = a.iterator();
do
{
if(!i$.hasNext())
break;
String temp = (String)i$.next();
if("1".equals(temp))
a.remove(temp);
} while(true);
看异常堆栈,是在Itr类的next()方法中的checkForComodification抛出的。Itr是ArrayList的一个内部类,其实现了Iterator接口。看一下抛异常的这个方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
确认是这里抛出的异常了。那么我们倒要看看,modCount和expectedModCount到底是什么意思。
expectedModCount是Iter的属性:
int expectedModCount = modCount;
可见,在开始迭代的时候,expectedModCount的值和modCount是相等的。而在迭代的过程中,出现了两者不一致的情况,导致checkForComodification方法抛出了异常。
在寻找哪里修改导致两者出现不一致的情况前,我们先看下modCount的含义。
modCount继承自AbstractList,初始值为0:
protected transient int modCount = 0;
看下remove方法方法中,对modCount和expectedModCount做了什么,注意,这里执行的是ArrayList的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 void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
对modCount进行了+1操作。所以在for循环执行到第二次时,modCount就比expectedCount大1了。
之所以出现这个问题,还是因为expectedCount是Itr的变量,而modCount是ArrayList的,当用ArrayList的remove(object)、add(object)方法时,没有同步更新expectedCount。看一下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 。