美文网首页Java 杂谈
Java集合的fail-fast机制

Java集合的fail-fast机制

作者: 落英坠露 | 来源:发表于2019-03-19 20:46 被阅读69次

    fail-fast 机制是集合世界中比较常见的错误检测机制,通常出现在遍历集合元素的过程中。它是一种对集合遍历操作时的错误检测机制,在遍历中途出现意外的修改时,通过 unchecked 异常暴力地反馈出来。这种机制经常出现在多线程环境下,当前线程会维护一个计数比较器, 即 expectedModCount, 记录已经修改的次数。在进入遍历前,会把实时修改次数 modCount 赋值给 expectedModCount,如果这两个数据不相等,则抛出异常。 java.util 下的所有集合类都是 fail-fast,而 concurrent 包中的集合类都是 fail-safe。

        public static void main(String[] args) {
            List<String> names = new ArrayList<>();
            names.add("one");
            names.add("two");
            names.add("three");
            for (String name : names) {
                if ("one".equals(name)) {
                    names.remove(name);
                }
            }
            System.out.println(names);
        }
    

    运行程序,出现异常:

    Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
    at java.util.ArrayList$Itr.next(ArrayList.java:857) at

    这是因为 ArrayList 内部的 Iterator 在执行 next 方法时,首先检查 modCount。而 ArrayList 的 remove 方法会使 modCount 加 1,这就导致 Iterator 内部的 expectedModCount 与 ArrayList 中的 modCount 不一致,所以抛出异常。

    那么移除元素的正确姿势是什么呢?就是使用 Iterator 的 remove 方法,它会修改游标位置和 expectedModCount。如果是多线程并发,还需要再 Iterator 遍历时加锁。

            while (iterator.hasNext()){
                synchronized (lock) {
                    String name = iterator.next();
                    if ("one".equals(name)) {
                        iterator.remove();
                    }
                }
            }
    

    COW(奶牛家族),即 Copy-On-Write,它是并发的一种新思路,实行读写分离,如果是写操作,则复制一个新集合,在新集合内添加或删除元素。待一切修改完成之后,再将原集合的引用指向新的集合。这样做的好处是可以高并发地对 COW 进行读和遍历操作,而不需要加锁,因为当前集合不会添加任何元素。使用 COW 时应注意两点:第一,尽量设置合理的容量初始值,它扩容的代价比较大;第二,使用批量添加或删除方法,如 addAll 或 removeAll操作,在高并发请求下,可以攒一下要添加或者删除的元素,避免增加一个元素复制整个集合。适用于读多写少的场景。

    看看 COW 添加元素的方法,内部实现一目了然,线程安全,写时复制。

        public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }
    

    COW 是 fail-safe 机制的,在并发包的集合申都是由这种机制实现的,fail-safe 是在安全的副本(或者没有修改操作的正本)上进行遍历,集合修改与副本的遍历是没有任何关系的,但是缺点也很明显,就是读取不到最新的数据。这也是 CAP 理论中 C(Consistency)与 A (Availability) 的矛盾,即一致性与可用性的矛盾。

    本文的一些内容来自:《码出高效:Java开发手册》,这时一本非常好的 Java 进阶书,强烈推荐多读几遍!

    相关文章

      网友评论

        本文标题:Java集合的fail-fast机制

        本文链接:https://www.haomeiwen.com/subject/mkcomqtx.html