当中途遇到失败时必须重试。
应用
错误示范
int target = 3;
Map<Integer,Integer> map = new HashMap<>();
map.put(1,3);
...
for(int e:map.keySet()){
if(e == target){
map.remove(e); // 会触发快速失败机制
}
}
//正确做法
Iterator<Map.Entry<Integer, Integer>> iter = map.entrySet().iterator();
while(iter.hasNext()){
Map.Entry<Integer, Integer> next = iter.next();
if(next.getKey()==target){
iter.remove();
}
}
原因
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ccc");
for (String s : list) {
System.out.println(s);
if (Objects.equals(s, "bbb")){
list.remove(s);
}
}
System.out.println(list);
触发快速失败机制,会抛出异常
使用迭代器的方法删除不会触发,解释如下:
迭代器的remove()方法执行结束之前会重新将modCount赋值给expectedModCount,
以保证不会触发快速失败机制。
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素的角标
int lastRet = -1; // 最后一个元素的角标,如果集合长度为0,则lastRet = -1
int expectedModCount = modCount;
public boolean hasNext() {
// 通过判断下一个元素的角标是否等于集合的大小来判断遍历过程中是否有下一个元素
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 遍历到当前元素的时候,会将cursor+1,使其指向下一个元素
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 迭代器的remove()方法执行结束之前会重新将modCount赋值给expectedModCount,
// 以保证不会触发快速失败机制。
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
// 快速失败机制
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
fail-safe与fail-fast的区别
当我们对集合结构上做出改变的时候,fail-fast机制就会抛出异常。但是,对于采用fail-safe机制来说,就不会抛出异常(大家估计看到safe两个字就知道了)。
这是因为,当集合的结构被改变的时候,fail-safe机制会在复制原集合的一份数据出来,然后在复制的那份数据遍历。
因此,虽然fail-safe不会抛出异常,但存在以下缺点:
- 复制时需要额外的空间和时间上的开销。
- 不能保证遍历的是最新内容。