【Java】【重要特性】详解fail-fast机制

举报
huahua.Dr 发表于 2022/09/28 23:25:11 2022/09/28
【摘要】 一、什么是fail-fast机制Fail-fast是快速失败机制,是java集合(Collection)中的一种错误检测机制,主要用于当迭代集合的过程中,该集合在结构上发生改变的时候,就有可能发生fail-fast机制,即抛出ConcurrentModificationException异常。Fail-fast机制并不能保证在不同步的修改下一定会抛出异常,它只是尽最大的努力抛出,所以这种机制...

一、什么是fail-fast机制

Fail-fast是快速失败机制,是java集合(Collection)中的一种错误检测机制,主要用于当迭代集合的过程中,该集合在结构上发生改变的时候,就有可能发生fail-fast机制,即抛出ConcurrentModificationException异常。Fail-fast机制并不能保证在不同步的修改下一定会抛出异常,它只是尽最大的努力抛出,所以这种机制一般只是用于检测bug。

二、fail-fast出现的场景

在我们常见的java集合中都有可能出现fail-fast机制,例如ArrayList,HashMap等线程不安全的集合,还有在多线程或单线程环境下都有可能出现fail-fast机制。

总的来说,可能会出现fail-fast机制的前提是一般在集合拿到迭代器迭代的过程中,而该集合元素又发生了变化,如迭代过程中集合的元素被删除修改或新增元素等,都会触发fail-fast机制,程序会尽可能快速抛出ConcurrentModificationException异常。

三、fail-fast实现原理

想要知道fail-fast的原理,那必须弄清楚它是怎么检测到集合结构发生变化并抛出ConcurrentModificationException的。首先先弄清楚一个问题:

怎么触发fail-fast机制?

我们都知道对于集合类、map类等容器,我们可以通过迭代器来遍历;

而迭代器是什么?它就是Iterator,其实就是一个接口,具体的实现是由集合类的内部类去实现Iterator接口并实现其相关方法的。

以ArrayList集合为例,在ArrayList类内部实现Iterator接口是内部类Itr:

//ArrayList调用iterator()方法的时候返回的是Itr的一个新实例  

 public Iterator<E> iterator() {

        return new Itr();

    }

 

    private class Itr implements Iterator<E> {

//cursor变量主要用于集合在遍历过程中执行即将遍历元素的索引

        int cursor;       // index of next element to return

//lastRet变量记录上一个遍历的元素的索引,不存在时为-1

        int lastRet = -1; // index of last element returned; -1 if no such

//该变量主要用于判断fail-fast机制了,用于记录集合操作过程中被修改的次数,初始值为AbstractList抽象类的modCount值,默认为0.

        int expectedModCount = modCount;

//1.用来判断是否有下一个元素,通过当前元素索引cursor是否等于size

        public boolean hasNext() {

            return cursor != size;

        }

 

        @SuppressWarnings("unchecked")

//2.next方法遍历下一个元素

        public E next() {

//2.1检查该集合元素是否发生改变,发生变化则触发fail-fast机制

            checkForComodification();

            int i = cursor;

            if (i >= size)

                throw new NoSuchElementException();

//2.2获取遍历的集合elementData[i]元素,游标cursor+1,返回

            Object[] elementData = ArrayList.this.elementData;

            if (i >= elementData.length)

                throw new ConcurrentModificationException();

            cursor = i + 1;

            return (E) elementData[lastRet = i];

        }

//3.删除遍历的第i个集合元素,可以避免直接调用集合类的remove方法而触发fail-fast机制

        public void remove() {

            if (lastRet < 0)

                throw new IllegalStateException();

            checkForComodification();

 

            try {

//3.1调用集合类里面的remove方法删除,但删除后会重写刷新游标cursor和记录lastRet值,相当于当前正在遍历的集合元素没有发生结构变化。

                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();

        }

//2.1检查在迭代遍历时集合元素是否被修改过了

        final void checkForComodification() {

//检查modCount值(相当于是最新的修改值,位于父类AbstractList的成员变量,实时更新变化)是否等于expectedModCount值(为modCount值的一份拷贝,在开始迭代时获取),如果不相等则说明集合发生改变了,抛出ConcurrentModificationException异常

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

        }

    }

四、如何避免fail-fast机制

知道了fail-fast机制的实现原理,那么我们就要去避免触发这种机制。

  1. 如果想要修改删除正在迭代的集合元素,必须使用迭代器里面提供的remove方法,而不是直接调用集合中的remove方法。
  2. 利用java.util.concurent包中的类来代替我们一般常用的集合类,如利用CopyOnWriteArrayList类代替ArrayList,使用ConcurrentHashMap类代替HashMap等,

CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。

对于HashMap,可以使用ConcurrentHashMap,ConcurrentHashMap采用了锁机制,是线程安全的。在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。