1.先看下最普遍遇到ConcurrentModificationException的情况
从异常信息可以发现,异常出现在checkForComodification()方法中。
List<Location> list = new ArrayList<>(); list.add(new Location("广东", 20)); list.add(new Location("辽宁", 20)); list.add(new Location("南京", 20)); Iterator<Location> iterator = list.iterator(); while (iterator.hasNext()) { Location next = iterator.next(); if ("广东".equals(next.getLocation())) { list.remove(next); } }
查看源码发现,checkForComodification()方法在ArrayList的iterator()方法中有具体实现,我截取了部分代码:这里能看到, iterator()方法内部会返回一个Itr类型的对象,出现异常真正的原因其实都在这里面了。
public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") 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]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
首先要清楚Iterator()中这三个成员变量的意义,具体可以参考源码的注释:
cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
lastRet:表示上一个访问的元素的索引
expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。
modCount是AbstractList类中的一个成员变量,初始值为0。还可以看到,Itr对象创建时,modCount会赋值给expectedModCount。
下面就是最重要的地方,可以看到调用迭代器的next()方法时内部会调用checkForComodification(), 方法内部逻辑很简单,比较expectModCount和modCount的值是否相等,不相等时就会抛出ConcurrentModificationException。
然后就是要知道modCount的含义,modCount值表示对List的修改次数,简单理解就是对集合增删的次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。
现在我们应该就知道在用迭代器遍历的时候用list的remove()和add()操作时引发异常的原因了,因为迭代器遍历每次在调用next()方法是第一行就会调用checkForComodification()去比较expectModCount和modCount的值是否相等,如果在迭代过程中对集合进行了remove()和add()操作使modCount的值递增,导致expectModCount和modCount产生差异,就会引发异常。增强for也是出现这种问题,因为底层也是迭代器 再看一遍next()的逻辑。
2.解决方式
Itr类中也给出了一个remove()方法,重点在于它有一个expectedModCount = modCount的操作。
改成这样就不会再报错了
3.在多线程环境下的解决方法(借鉴大佬的帖子)
上面的解决办法在单线程环境下适用,但是在多线程下适用吗?看下面一个例子:
public class Test {
static ArrayList<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Thread thread1 = new Thread(){
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
};
Thread thread2 = new Thread(){
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove();
}
};
};
thread1.start();
thread2.start();
}
}
运行结果:
有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。
原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。(注:这里的两个线程共用的是同一个list,操作的是同一个modCount成员变量)
因此一般有2种解决办法:
1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;
2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。