fail-fast机制是java集合中的一种机制,当迭代集合过程中,集合的结构发生了改变(增删改元素),就可能会发生错误。
首先我们看两个例子
public class FailFastTest {
public static void main(String[] args) {
ArrayList a=new ArrayList();
a.add(1);
a.add(2);
a.add(3);
a.add(4);
for (Object num:
a) {
a.remove(0);
}
}
}
抛出异常ConcurrentModificationException
public class FailFastTest {
public static void main(String[] args) {
ArrayList a=new ArrayList();
a.add(1);
a.add(2);
a.add(3);
a.add(4);
Iterator iterator=a.iterator();
for (int i = 0; i <4 ; i++) {
iterator.next();
iterator.remove();
}
}
}
不抛出异常
为什么都是迭代元素,第一个例子会抛出异常第二个不会呢,就是因为第一个触发了Java的fail-fast机制
//获取迭代器
Iterator iterator=a.iterator();
public Iterator<E> iterator() {
return new Itr();
}
我们先来看看ArrayList是怎么获得迭代器的
new一个Itr
以下是ltr的源码
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
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 = 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;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我们看到三个变量,一个是cursor,即下一个指针的下标
lastRet 上一个指针的下标
eexpectedModCount是期望修改次数,初始化为modCount
而modCount就是修改次数
关键就是最后这个 checkForComodification()函数
当迭代器进行next,会先调用 checkForComodification(),检查预期修改次数跟当前修改次数也就是modCount是否相符:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
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 = i + 1;
return (E) elementData[lastRet = i];
}
当我们在foreach中调用remove
public E remove(int index) {
rangeCheck(index);
modCount++;//注意这句
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
modCount会+1,所以我们的foreach向前移动,也就是调用next函数时,发现modCount跟eexpectedModCount不符合,就会抛出异常。
为什么在迭代器Iterator中remove就没关系呢。因为Iterator移除的是lastRet下标的,也就是迭代器上一个访问的对象,已经访问过了的对象。
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; //注意这句
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
这个remove方法调用ArrayList的remove方法,modCount会+1但是
它有一句操作 expectedModCount = modCount; 也就是把expectedModCount重新赋值。所以也就不会抛出异常。
为什么要这样设计呢?我们再来看一个例子
ArrayList a=new ArrayList();
a.add(1);
a.add(2);
a.add(3);
a.add(4);
Thread thread=new Thread(()->{
Iterator iterator=a.iterator();
iterator.next();
System.out.println("----");
iterator.remove();
});
thread.start();
for (Object num:
a) {
System.out.println(num);
}
这个例子是多线程同时访问同一个ArrayList,
线程的执行速度是不确定的
我们看两种输出
一种是:
1
----
2
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at FailFastTest.main(FailFastTest.java:20)
一种是:
1
2
3
4
---
第一种抛出异常第二种不抛出异常,看到这里你大概已经明白了fail-fast的作用。第一种输出是第二个foreach遍历时,第一个iterator改变了内部结构,第二种输出则是iterator在foreach遍历循环结束后才修改集合内部元素。
failfast是用来保证迭代器遍历时,集合的一致性,即正在遍历的集合结构没有发生变化。Iterator接口的remove之所以这样设计,我认为是为了要留出一个接口或者说功能来remove元素。所以我们在多线程下尽量使用Iterator对集合进行操作。
参考连接:https://blog.csdn.net/zymx14/article/details/78394464