场景
有如下代码运行:
@Test
public void testRemove() {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
// 当为1时运行正常,改成2时异常ConcurrentModificationException
if ("2".equals(item)) {
list.remove(item);
}
}
System.out.println(PrintUtils.parintArrayList(list));
}
异常如下:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at NormalTest.testRemove(NormalTest.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
...
分析
将字节码反编译
上图发现,我们简简单单的一句for (String item : list) {
在真实的字节码中却变成了红框中的部分,进一步分析。
跟踪源码
第一行
public Iterator<E> iterator() {
// ArrayList中的一个内部类
return new Itr();
}
在创建该对象时有这么一句赋值语句
该字段在ArrayList中标识当前对象的修改次数(包括remove和add方法),跟踪代码不难发现都有这么一行代码modCount++;
第二行
查看while条件中的源码,并不会发生异常,暂且不管
public boolean hasNext() {
return cursor != size;
}
第三行
仔细查看Itr
代码中的next
实现
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();
}
结合上面的判断,在创建Itr对象时就将ArrayList的修改次数modCount
赋值给Itr迭代器对象,如果在迭代期间ArrayList对象被操作(remove和add)了导致modCount
值修改,就会报异常ConcurrentModificationException
。
再分析
那么为什么当判断条件为“1”时 if ("1".equals(item)) {
就不会报错呢?
查看while条件中的源码涉及到两个字段cursor
和size
,做个表格来一步一步记录下值的变化
public boolean hasNext() {
return cursor != size;
}
判断条件为if ("1".equals(item)) {
注:因为是两次add,所以导致了expectedModCount
和modCount
初始值为2
迭代次数 | cursor | size | expectedModCount | modCount |
---|---|---|---|---|
新创建 | 0 | 2 | 2 | 2 |
第一次next | 1 | 2 | 2 | 2 |
执行remove 操作后 | 1 | 1 | 2 | 3 |
以上就能看到,当remove
操作后就导致了cursor
和size
一致,也就退出了while循环,避免了异常抛出。
判断条件为if ("2".equals(item)) {
迭代次数 | cursor | size | expectedModCount | modCount |
---|---|---|---|---|
新创建 | 0 | 2 | 2 | 2 |
第一次next | 1 | 2 | 2 | 2 |
第二次next | 2 | 2 | 2 | 2 |
执行remove 操作后 | 2 | 1 | 2 | 3 |
while 判断条件仍为true ,继续执行 | 2 | 1 | 2 | 3 |
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
所以在next
代码块中的checkForComodification
就抛出了异常,以上也就完美了解释了为什么当判断条件为2时抛异常了。