java基础提升之 fail-fast

        在查看集合类源码时,fail-fast这个词出现的频率很高,几乎每个集合类中都会出现,好比ArrayList、HashMap、HashSet、LinkedHashMap等。这一篇文章咱们未来讨论一下什么是fail-fast以及fail-fast的实现原理。java

什么是fail-fast

        fail-fast(快速失败机制)是java集合中的一种错误机制。在HashMap中有这么一段描述fail-fast的机制的注释文字:并发

The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a {@link ConcurrentModificationException}. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future. 函数

        上面这段英文告诉了咱们fail-fast是如何出现的,其中提到当一个迭代器被建立(这里咱们单纯的认为是调用了HashMap的keySet()、values()等方法后根据返回的结果又调用了iterator方法)后,若是map的结构被修改(此时的结构修改是指map的size发生变化,好比扩容、新建或者删除了其中的元素,更新不算结构修改)。那么在返回的iterator中进行遍历会抛出一个 ConcurrentModificationException错误。这就是快速失败机制。所以,在并发修改的状况下,迭代器快速而干净的失败,而不是在将来的未肯定时间冒着任意的非肯定行为的风险。ui

        对于为什么对原集合类操做会影响到以前返回的iterator咱们会在下面进行说明。this

        另外在HashMap解释完fail-fast后又紧跟着来了这么一段:spa

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs. code

        这段的大体意思是,迭代器的fail-fast机制并不能被保证必定会发生,通常来讲,在存在不一样步的并发修改时不可能作出任何有力保证,可是fail-fast会尽最大努力抛出ConcurrentModificationException错误。所以若是一个程序依赖这个异常去保证其运行的正确性是错误的,快速失败机制只能用来标识这个错误。cdn

        在源码中就好比HashMap的keySet()方法,在注释中明确提出了,这个方法返回的集合由HashMap支持,因此任何对map的更改的结果都会反映到返回的set上,反之也同样。咱们经过代码来验证这一段话blog

package com.lichaobao.collectionsown.collections;

import java.util.*;
import java.util.stream.Stream;

/** * @author lichaobao * @date 2019/5/11 * @QQ 1527563274 */
public class MapTestMain {
    public static void main(String[] args){
        Map<String, String> map = new HashMap<>();
        String b = map.put("2","2Value");
        String c = map.put("3","3Value");
        String a = map.put("1","1Value");
        String e = map.put("5","5Value");
        String f = map.put("6","6Value");
        String d = map.put("4","4Value");
        Set<String> set = map.keySet();
        System.out.println("初始");
        System.out.println(map.toString());
        System.out.println(set.toString());
        System.out.println("===============");
        System.out.println("set删除1");
        set.remove("1");
        System.out.println(map.toString());
        System.out.println(set.toString());
        System.out.println("===============");
        System.out.println("map删除3");
        map.remove("3");
        System.out.println(map.toString());
        System.out.println(set.toString());
        System.out.println("===============");
    }
}

复制代码

运行后返回结果以下:rem

咱们能够发现不论是对set操做仍是对源map进行操做,最后结果都会反映到对方身上。

        那么为何会这样呢,咱们来看一下在HashMap中的KeySet方法:

        咱们发现返回的实际上是HashMap中的KeySet内部类咱们再来看一下HashMap类中KeySet类:

        发现啥没?没错,其实全部的KeySet的操做都是调用HashMap的方法来完成的,这就是为何对返回的KeySet中的操做会反映到HashMap以及对HashMap进行操做也会反映到KeySet的缘由。在我我的认为这也是fail-fast机制出现的根本缘由。

fail-fast 实现原理

        咱们经过HashMap来讨论fail-fast机制。查看HashMap咱们能够发现HashMap中有这么一个变量:

        就是这个modCount协助完成了fail-fast机制。关于modCount咱们能够看到注释中是这样写的:记录了这个HashMap结构修改的次数。结构修改指的是更改HashMap中的键值对的数量以及其余方式修改器内部结构(如rehash)的修改。这个字段被用来在HashMap的迭代器上作快速失败。你们能够在HashMap的源码中查看什么时刻modCount被修改了,去验证这一句话。

只有一个modCount还不足以完成fail-fast。咱们上文说到,快速失败是在Iterator迭代中产生的。那咱们看看在HashMap中内置的Iterator类:

abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }
复制代码

        没错就是这个抽象类 HashIterator。keySet()、values()、entrySet()方法都是依靠这个类来完成的。如今咱们把关注点放在这个抽象类的一个字段上:

        就是红圈圈住的expectedModCount 源码再次很贴心得告诉了咱们这个字段用来作fast-fail,那具体这个字段是怎么用的呢? 咱们来看HashIterator的构造函数:

        咱们发如今Iterator生成的时候这个expectedModCount被赋上了modCount的值。好了,铺垫了这么多,咱们终于能够说到重点,那就是fail-fast机制究竟是如何实现的,咱们把关注点放在HashIterator类中的nextNode()以及remove方法上:

其实如今就很清楚了,当Iterator类内部的expectedModCount!=modCount时就会抛出ConcurrentModificationException。咱们如今用代码来演示一下fail-fast:

package com.lichaobao.collectionsown.collections;

import java.util.*;
import java.util.stream.Stream;

/** * @author lichaobao * @date 2019/5/11 * @QQ 1527563274 */
public class MapTestMain {
    public static void main(String[] args){
        Map<String, String> map = new HashMap<>();
        String b = map.put("2","2Value");
        String c = map.put("3","3Value");
        String a = map.put("1","1Value");
        String e = map.put("5","5Value");
        String f = map.put("6","6Value");
        String d = map.put("4","4Value");
        Set<String> set = map.keySet();
        Iterator<String> iterable = set.iterator();
        System.out.println("初始");
        System.out.println(map.toString());
        System.out.println(set.toString());
        System.out.println("===============");
        System.out.println("set删除1");
        set.remove("1");
        System.out.println(map.toString());
        System.out.println(set.toString());
        System.out.println("===============");
        while (iterable.hasNext()){
            System.out.println(iterable.next());
        }
    }
}

复制代码

        如今咱们来看一下运行结果:

        本篇结束!

相关文章
相关标签/搜索