引论:
当我们对一个List集合用迭代器或增强for循环遍历同时对集合内容进行修改,并导致集合长度改变(add,remove),则会出现 ConcurrentModificationException异常,普通for循环时修改却不会报错,为什么?
错误信息:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)即其中next和checkForComodification处出现问题
代码演示1:迭代器遍历方式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s = it.next();
if (s.equals("java")){
list.remove(s);
}
}
System.out.println(list);
}
//Exception in thread "main" java.util.ConcurrentModificationException
代码演示2:增强for遍历方式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
for (String s : list) {
if (s.equals("java")){
list.remove(s);
}
}
System.out.println(list);
}
//Exception in thread "main" java.util.ConcurrentModificationException
代码演示3:普通for遍历方式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("java")){
list.remove(list.get(i)));
}
}
System.out.println(list);
}
//[hello, world, hello]
源码分析:
//List中的相关方法
public interface List<E>{
Iterator<E> iterator();
boolean add(E e);
}
public abstract class AbstractList<E>{
protected int modCount = 0;
}
//ArrayList实现List集合,并重写的相关方法,抽取其中对分析并发修改异常的源码
public class ArrayList<E> extends AbstractList<E> implements List<E>{
public E remove(int index) { //remove()方法源码 // 6 调用remove()方法
rangeCheck(index);
modCount++; //注意看此处 // 7 modCount+1 变为1
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public Iterator<E> iterator() { // 1 访问Iterator()方法
return new Itr(); // 2 返回Itr对象
}
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
/*
modCount:实际修改集合次数 从AbstractList继承而来,默认是0
expectedModCount:预期修改集合的次数
*/
Itr() {}
public E next() { // 3 使用Itr对象的next()方法 // 8 再次使用next()方法
checkForComodification();// 4 调用checkForComodification()方法 // 9 再次校验
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)//5 使用remove()方法前校验正常//10 第二次校验失败
throw new ConcurrentModificationException();
}
}
}
分析结论:
在每次使用next()方法时,都会校验modCount与expectModCount是否相等,若是中间有对集合的remove和add等操作,则modCount会进行加1操作,下次校验不会通过,抛出并发修改异常。增强for循环底层使用的是迭代器遍历,因此依然会出现此异常。普通for循环不使用迭代器,因此可以在遍历过程中修改集合。即只要是迭代器遍历,就不允许在迭代过程中使用除迭代器自己的remove方法之外的方法修改集合结构,除非设法越过检测。
拓展:
之所以会有这个校验,是因为防止一个用户在使用该集合时修改导致了此集合结构的改变,而使其他用户使用时出现异常,这便是Java的fail-fast机制,为避免出现这个异常可以使用fail-safe,即对副本进行遍历修改,随后再整体赋值给原来集合。但fail-safe会消耗内存