天天看点

为什么在foreach循环中进行元素remove/add操作,会抛ConcurrentModificationException 异常?场景分析

场景

有如下代码运行:

@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:)
    at java.util.ArrayList$Itr.next(ArrayList.java:)
    at NormalTest.testRemove(NormalTest.java:)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:)
    ...
           

分析

将字节码反编译

为什么在foreach循环中进行元素remove/add操作,会抛ConcurrentModificationException 异常?场景分析

上图发现,我们简简单单的一句

for (String item : list) {

在真实的字节码中却变成了红框中的部分,进一步分析。

跟踪源码

第一行

public Iterator<E> iterator() {
        // ArrayList中的一个内部类
        return new Itr();
    }
           

在创建该对象时有这么一句赋值语句

为什么在foreach循环中进行元素remove/add操作,会抛ConcurrentModificationException 异常?场景分析

该字段在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 + ;
            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
新创建 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
新创建 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时抛异常了。

继续阅读