参考资料
1 ConcurrentModificationException异常 出现的原因
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
}
}
运行结果:
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)
at Polymorphic.Test.main(Test.java:13)
expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount
modCount
是AbstractList类中的一个成员变量
protected transient int modCount = 0;
该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。
- next() 方法
在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。
- remove() 方法
remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。
注意此时各个变量的值:对于iterator,其expectedModCount为0,cursor的值为1,lastRet的值为0
对于list,其modCount为1,size为0。
总结:关键在于
调用list.remove()方法导致modCount和expectedModCount的值不一致
。
像使用for-each进行迭代实际上也会出现这种问题
2 在单线程环境下的解决办法
在Itr类中也给出了一个remove()方法,在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作
expectedModCount = modCount;
因此,在迭代器中如果要删除元素的话,需要调用Itr类的remove方法
。
将上述代码改为下面这样就不会报错了:
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(integer); //注意这个地方
}
}
}
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();
}
}
运行结果:
1
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at Polymorphic.Test$1.run(Test.java:21)
有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误
原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了
一般有 2种解决办法:
- 在使用iterator迭代的时候使用synchronized或者Lock进行同步
- 使用并发容器CopyOnWriteArrayList代替ArrayList和Vector