1 并发修改异常(ConcurrentModificationException)
当在对集合进行迭代的同时改变集合的结构(如增删操作)或者当多线程在结构上对集合进行改变时,就会抛出并发修改异常。
下面的就是经典的在迭代过程中修改了集合的结构导致的并发修改异常。
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer it = iterator.next();
if(it == 1){
list.remove(it);
}
System.out.println(it);
}
}
}
1
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:939)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:893)
at com.hr.edu.ArrayListTest.main(ArrayListTest.java:15)
2 集合是怎么检测并发修改异常的
集合通过跟踪修改操作的次数,集合中有一个变量modCount用于记录集合添加或修改的次数。每个迭代器内部也维护着一个独立的计数器expectedModCount。在每个迭代器方法的开始处会检查两个参数是不是一致,如果不一致,则会抛出一个并发修改异常。
// AbstractArrayList中的一个变量,用于记录集合结构被修改的次数。
protected transient int modCount = 0;
ArrayList的add()和remove(),可以看到,每次调用一次add()或remove方法,modCount属性值加1,表示集合被修改了一次。
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++; // 每次添加一个元素modCount加1
...
}
public E remove(int index) {
Objects.checkIndex(index, size);
modCount++; // 每次删除一个元素modCount加1
...
}
Iterator部分源码
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
...
// 期望集合修改次数
int expectedModCount = modCount;// 迭代器中的expectedModCount值和modCount是相等的。
// 此方法就是抛出并发修改异常的方法
// 该方法会判断expectedModCount 和 modCount是否相等,如果不相等抛出并发修改异常
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();// 每次调用迭代器的next()方法都会判断一次
...
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();// 每次调用迭代器的remove()方法都会判断一次
...
}
}
现在再看上面的测试代码,刚开始modCount的值为0,ArrayList连续调用3次add(),此时modCount的值为3。当执行 Iterator<Integer> iterator = list.iterator()时,初始化一个迭代器对象,此时expectedModCount的值和modCount的值相等都为3,。
第1次调用iterator.next()会检查modCount和expectedModCount两个值是否相等,此时两个变量的值都是3,所以不会抛异常。第1次的值是1,if条件满足,此时执行list.remove()方法,所以modCount的值就会加1,modCount=4,这就导致了modCount和expectedModCount两个值不在相等。所以在第二次调用iterator.next()方法时,checkForComodification()就会抛并发修改异常。
3 解决并发修改异常
单线程下使用迭代器的修改方法。在迭代过程中,如果使用ArrayList类中的remove()会使modCount的值就会加1而导致modCount和expectedModCount两个值不相等。但是迭代器自己也实现了remove(),所以在迭代过程中,使用迭代器提供的remove()方法可以避免并发修改异常。
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer it = iterator.next();
if(it == 1){
iterator.remove();// 使用了迭代器的删除方法
}
}
System.out.println(list); // [2,3]
}
}
在多线程环境下,可以使用并发容器。
4 fast-fail && fast-safe
fast-fail快速失败机制,它是Java集合中一种错误检测机制,当多线程在结构上对集合进行改变时,就可能会产生fail-fast机制并抛出并发修改异常。java.util包下的集合类都是按fail-fast来设计的。
而fast-safe在集合结构被修改时不会抛出异常,这是因为当集合机构被改变时,fail-safe机制会复制原集合的一份数据出来,然后在复制那份数据遍历。
因此,fast-safe存在以下缺陷:复制需要额外的空间和时间开销;不能保证遍历的集合是最新的。
本文完
如发现错误,请指正!!!
网友评论