快速失败 (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.ConcurrentModificationException
at 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.ConcurrentModificationException
at 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的高效读取与安全更新