使用迭代器遍历List的时候修改List报ConcurrentModificationException异常原因分析

简介:

 在使用Iterator来迭代遍历List的时候如果修改该List对象,则会报java.util.ConcurrentModificationException异常,下面看一个例子演示:


 1 package com.others;
 2
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5 import java.util.List;
 6 import java.util.concurrent.CopyOnWriteArrayList;
 7
 8 public class ArrayListTest {
 9
10     public static void main(String[] args) {
11         List list = new ArrayList();
12         //CopyOnWriteArrayList list = new CopyOnWriteArrayList();
13         list.add(“a”);
14         list.add(“b”);
15         list.add(“c”);
16         list.add(“d”);
17         list.add(“e”);
18         Iterator iterator = list.iterator();
19         while(iterator.hasNext()){
20             String str = (String) iterator.next();
21             if(str.equals(“c”)){
22                 list.remove(str);
23             }else{
24                 System.out.println(str);
25             }
26         }
27     }
28
29 }

结果为:
a
b
Exception in thread “main” java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at com.others.ArrayListTest.main(ArrayListTest.java:20)

  当调用list的iterator()方法的时候,返回的是一个Itr对象(实现了Iterator接口):

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

  我们看一下Itr这个类:

 1 private class Itr implements Iterator {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount; //刚创建迭代对象的时候List的modCount
 5
 6         public boolean hasNext() {
 7             return cursor != size;
 8         }
 9
10         @SuppressWarnings(“unchecked”)
11         public E next() {
12             checkForComodification(); //每调用一次next()函数都会调用checkForComodification方法判断一次
13             int i = cursor;
14             if (i >= size)
15                 throw new NoSuchElementException();
16             Object[] elementData = ArrayList.this.elementData;
17             if (i >= elementData.length)
18                 throw new ConcurrentModificationException();
19             cursor = i + 1;
20             return (E) elementData[lastRet = i];
21         }
22
23         public void remove() {
24             if (lastRet < 0)
25                 throw new IllegalStateException();
26             checkForComodification();
27
28             try {
29                 ArrayList.this.remove(lastRet);
30                 cursor = lastRet;
31                 lastRet = -1;
32                 expectedModCount = modCount;
33             } catch (IndexOutOfBoundsException ex) {
34                 throw new ConcurrentModificationException();
35             }
36         }
37         //此方法用来判断创建迭代对象的时候List的modCount与现在List的modCount是否一样,不一样的话就报ConcurrentModificationException异常
38         final void checkForComodification() {
39             if (modCount != expectedModCount)
40                 throw new ConcurrentModificationException();
41         }
42     }

 List对象有一个成员变量modCount,它代表该List对象被修改的次数,每对List对象修改一次,modCount都会加1.

  Itr类里有一个成员变量expectedModCount,它的值为创建Itr对象的时候List的modCount值。用此变量来检验在迭 代过程中List对象是否被修改了,如果被修改了则抛出java.util.ConcurrentModificationException异常。在每 次调用Itr对象的next()方法的时候都会调用checkForComodification()方法进行一次检验,checkForComodification() 方法中做的工作就是比较expectedModCount 和modCount的值是否相等,如果不相等,就认为还有其他对象正在对当前的List进行操 作,那个就会抛出ConcurrentModificationException异常。

  我们再来分析一下上面那个例子,当例子程序执行到22行的时候,将list对象里面的“c”删除了,同时list对象的modCount值加1,但是Itr对象的expectedModCount没有变,他们肯定是不相等了。等再一次执行next()方法的时候调用了checkForComodification()方法,这时候就抛出异常了。

  我们再将上面那个例子稍微改动一下:将21行改为if(str.equals(“d”)){,即删除”d”这个元素。运行结果如下:

a

b

c

  这时却没有报异常了,但是“e”却没有输出来,这是为什么呢?原因很简单,我们看到Itr的hashNext()方法:

1 public boolean hasNext() {
2             return cursor != size;
3         }

 它是通过Itr的对象的cursor与List对象的size值来判断是否还有未迭代的对象,当遍历完“d”的时候cursor=4,删除”d” 的时候,List对象的size就会减1,size首先为5,后来变为4,这时候cursor和size是相等的,hasNext()方法返回的是 false,就认为遍历结束了,所以删除以后没有进去执行next()方法了,就没有抛出异常了,当然”e”也没有输出来。

  为了避免这种异常,我们可以使用CopyOnWriteArrayList来代替ArrayList,CopyOnWriteArrayList支持并发访问,所以同时进行迭代和修改是没有问题的。

相关文章
|
3月前
|
存储 Java
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
|
3月前
|
编译器
【Bug记录】list模拟实现const迭代器类
【Bug记录】list模拟实现const迭代器类
|
5天前
|
Java 程序员 编译器
Java|如何正确地在遍历 List 时删除元素
从源码分析如何正确地在遍历 List 时删除元素。为什么有的写法会导致异常,而另一些不会。
14 3
|
5月前
|
索引
List集合(方法简介,集合遍历)
List集合(方法简介,集合遍历)
|
5月前
|
编译器 C语言 C++
C++ STL中list迭代器的实现
C++ STL中list迭代器的实现
C++ STL中list迭代器的实现
|
5月前
|
Java 索引
JavaSE——集合框架一(3/7)-List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
JavaSE——集合框架一(3/7)-List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
42 2
|
5月前
|
C++ 容器
【c++】优先级队列|反向迭代器(vector|list)
【c++】优先级队列|反向迭代器(vector|list)
34 0
|
5月前
|
存储 缓存 编译器
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
35 0
|
5月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
823 1
|
4月前
|
Java API Apache
怎么在在 Java 中对List进行分区
本文介绍了如何将列表拆分为给定大小的子列表。尽管标准Java集合API未直接支持此功能,但Guava和Apache Commons Collections提供了相关API。