快速失败 (fail-fast) 是什么?
fail-fast 是一种错误检测机制,它的核心思想是在检测到错误条件时立即抛出异常,以防止程序进一步执行可能导致错误的操作。通常用于迭代器以及集合类中。当你用迭代器对集合进行迭代时,如果集合在迭代过程中被修改了,就会立即抛出ConcurrentModificationException
。这种修改可能是添加、删除集合中的元素。快速失败机制通过立即通知程序员发生了并发修改,避免了未定义行为的出现。
案例 1
public static void main(String[] args) {ArrayList<Integer> numbers = new ArrayList<>();numbers.add(1);numbers.add(2);numbers.add(3);// 创建迭代器Iterator<Integer> iterator = numbers.iterator();// 在迭代器创建后对列表进行修改numbers.add(4);// 再次输出迭代器的值while (iterator.hasNext()) {System.out.println(iterator.next()); // 第一次进来时报错}}
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)at org.swp.controller.UserContext.main(UserContext.java:15)
迭代器创建之后,通过调用numbers.add(4)修改了列表的结构,从而在随后的迭代中引发了ConcurrentModificationException
。
案例 2
public static void main(String[] args) {ArrayList<Integer> numbers = new ArrayList<>();numbers.add(1);numbers.add(2);numbers.add(3);// 其实在代码编译的时候for循环会被编译成迭代器for(Integer a : numbers){ // 第二次遍历时报错if(a.equals(1)){numbers.remove(a);}}
}
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)at org.swp.controller.UserContext.main(UserContext.java:15)
当迭代器正在遍历numbers列表时,你尝试从中删除元素。这会改变列表的结构,因此触发了ConcurrentModificationException
。
如何避免这个异常
要修复这种错误,你需要确保在迭代期间不直接修改集合。如果你必须修改集合,你有几个选项:
- 在迭代期间收集要删除的元素,然后在迭代后删除它们。
- 使用迭代器提供的remove()方法来移除元素。
- 使用支持并发修改的集合类,如ConcurrentHashMap或CopyOnWriteArrayList。
- 在for循环中引入变量,而不是遍历集合
ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);for(int i=0;i<list.size();i++) {Integer item = list.get(i);if (item.equals(3)) {list.remove(i);//为了效率,这里最好不要用list.remove(item)}}
不引用迭代器方法进行删除,可以防止ConcurrentModificationException
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {Integer a = iterator.next();if (a.equals(1)) {iterator.remove();}
}
使用迭代器的remove()方法来删除当前元素可以防止ConcurrentModificationException
,因为这样做不会破坏迭代器的内部状态。
public static void main(String[] args) {CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();list.add(1);list.add(2);list.add(3);Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {Integer a = iterator.next();if (a.equals(1)) {list.remove(a);}}System.out.println(list);}
CopyOnWriteArrayList 源码推荐阅读文章
文章链接: 解析CopyOnWriteArrayList的高效读取与安全更新