Java 迭代器删除元素ConcurrentModificationException异常。

时间:2022-09-03 16:26:04

Java是不支持容器类在使用迭代器迭代过程中,使用如 list.remove(obj)方法删除元素。否则会抛出ava.util.ConcurrentModificationException异常。应该使用iterator.remove()方法删除当前迭代到的元素。

这是因为Java集合中有一种叫fail-fast的机制,即如果多个线程对同一个集合的内容进行操作时,则会产生fail-fast事件,即抛出异常。

比如下面代码

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Hello {

    public static void main(String[] args) {
        List<String> all=new ArrayList<String>();
        all.add("Hello");
        all.add("_");
        all.add("World!!");
        Iterator<String> iterator=all.iterator();
        while(iterator.hasNext())
        {
            String str=iterator.next();
            if("Hello".equals(str))
            {
                all.remove(str);

            }
            else
            {
                System.out.println( str+" ");
            }
        }
        System.out.println("\n删除\"_\"之后的集合当中的数据为:"+all);
    }
}

删除元素会抛出下面异常

Java 迭代器删除元素ConcurrentModificationException异常。

这里在迭代过程中改变了原来集合的存储内容,因此出发了fail-fast机制。

fail-fast机制是这样设置的。以上面代码抛出的异常为例。查看ArrayList.类的源码,可以看到抛出异常的函数为

Java 迭代器删除元素ConcurrentModificationException异常。

这里的modCount文档是这样解释的

The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.

大概意思就是list的结构被修改的次数。结构的修改包括改变list的大小,或者其他可能使迭代结果产生错误的干扰行为。

expectedModCount注释为:

The modCount value that the iterator believes that the backing List should have.  If this expectation is violated, the iterator has detected concurrent modification.

因此若是在迭代过程中改变了List的结构,即使用了remove,或者等add方法则会导致expectedModCount的值与modCount的值不同,就会抛出异常。

但是有一个奇特的现象,如果我们使用List.remove(obj)方法删除倒数第二个元素则不会超出异常。如

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Hello {

    public static void main(String[] args) {
        List<String> all=new ArrayList<String>();
        all.add("Hello");
        all.add("_");
        all.add("World!!");
        Iterator<String> iterator=all.iterator();
        while(iterator.hasNext())
        {
            String str=iterator.next();
            if("_".equals(str))
            {
                all.remove(str);

            }
            else
            {
                System.out.println( str+" ");
            }
        }
        System.out.println("\n删除\"_\"之后的集合当中的数据为:"+all);
    }
}

注意这里删除的'_'为集合中的倒数第二个元素。同时注意代码有,若没有删除此元素,则输出此元素,即else里面的内容。结果为

Java 迭代器删除元素ConcurrentModificationException异常。

不是说,不能用List.remove(obj)的方式,只能用iterator.remove()的方式删除,这里为什么没有抛异常。原因是这样。

看ArrayList的hasNext()方法与next()方法的源码

size为当前集合的大小,cursor为下个元素的索引,初始化为0

Java 迭代器删除元素ConcurrentModificationException异常。

其中checkForComodification的源码为,两个变量的的解释上面也已经说了。

Java 迭代器删除元素ConcurrentModificationException异常。

我们的迭代器部分代码按下面步骤执行:

all里面存储了“Hello”,"_","world!"三个元素

  1. iterator.hasNext(),
    1. cursor=0,size=3。cursor!=size,返回true,进入while内
  2. String str=iterator.next(),
    1. checkForComodification(),此时modCount=0,expectedModCount=0,因此没有抛出异常,
    2. cursor=i+1,即cursor=1
    3. return elementData[lastRet=i],str='Hello'
  3. '_'.equals(str)为False,输出Hello
  4. iterator.hasNext(),
    1. cursor=1,size=3。cursor!=size,返回true,进入while内
  5. String str=iterator.next(),
    1. checkForComodification(),此时modCount=0,expectedModCount=0,因此没有抛出异常,
    2. cursor=i+1,即cursor=2
    3. return elementData[lastRet=i],str='_'
  6. **'_'.equals(str)为Ture,**
    1. all.remove(str)
    2. modCount=1,因为改变了List的结构,因此modCount+1。
  7. iterator.hasNext(),
    1. cursor=2,size=2。cursor!=size,返回False,退出

可以看到,在这里就已经推出while循环了。不会在执行checkForComodification()方法,判断List结构有没有改变。

结论:因此只要对倒数第二个元素执行remove()方法,虽然会改变集合结构,但是因为也同时改变了集合size,使size与cursor的大小一样,就会直接循环,不会报异常。