前言:
遍历集合根据某个条件对集合内容进行修改,这是一个非常常用的情景,但在实际开发中有时会抛出ConcurrentModificationException有时候又不会。这里终结一下。
例子(一):
public class ArrayListTest {
public static void main(String[] args) {
List<Integer> listA=new ArrayList<>();
listA.add(1);
listA.add(2);
listA.add(3);
listA.add(4);
listA.add(5);
listA.add(6);
removeThing01(listA,3);
}
public static void removeThing01(List<Integer> list,Integer value){
for (Integer integer : list) {
if (integer.equals(value)){
list.remove(value);
}
}
}
}
抛出异常:

这是在单线程的情况下,抛出ConcurrentModificationException异常,那么问题到底出在哪里呢?
这里就需要提一下Java的快速失败和安全失败
一:快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改或者迭代过程中被修改。
二:安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
原理:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
在next方法执行的时候,会执行checkForComodification()方法

所以刚刚的例子我们的问题是在迭代过程中修改了集合,导致了modCount不等于expectedModCount。所以这里的解决方案是:改为迭代器方式来修改集合。
public static void removeThing02(List<Integer> list,Integer value){
Iterator<Integer> iterator=list.iterator();
while (iterator.hasNext()){
Integer integer=iterator.next();
if (integer.equals(value)){
iterator.remove();
}
}
}
原理:每次通过迭代器来remove或者add都会修改expectedModCount的值。

例子(二):
public class ArrayListTest {
public static void main(String[] args) {
List<Integer> listA=new ArrayList<>();
listA.add(1);
listA.add(2);
listA.add(3);
listA.add(4);
listA.add(5);
listA.add(6);
new Thread(() -> {
try {
removeThing02(listA,3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
removeThing02(listA, 4);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
public static void removeThing02(List<Integer> list,Integer value) throws InterruptedException {
Iterator<Integer> iterator=list.iterator();
while (iterator.hasNext()){
Integer integer=iterator.next();
Thread.sleep(1000);
if (integer.equals(value)){
iterator.remove();
}
}
}
}

问题出在哪?很明显是因为Iterator的remove,next方法都不是线程安全的,所以还是有机会触发modCount变量不等于expectedmodCount值抛出异常。
所以这里的解决方案是:
1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;
2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。
3)使用并发容器ConcurrentHashMap代替hashMap

1077

被折叠的 条评论
为什么被折叠?



