Java的fail-fast机制是什么?以ArrayList为例

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

猜你喜欢

转载自blog.csdn.net/weixin_44137260/article/details/105989940
今日推荐