JDK集合fail-fast机制源码解读

前情

      这两天一哥们在面试国内大厂问到了,小伙子你知道fail-fast是啥吗??小伙子一脸懵逼,随口来了句中式翻译,“错的快”???,此时此刻的面试官也是一脸懵逼,说道,你觉得怎么走最快就怎么走吧。。。。哈哈,简单的开个玩笑。其实我之前只知道这个玩意的名称,但是不知道底层实现原理是啥,今天我们就来看看到底发生了啥

简述

      fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当使用迭代器时候,进行一定的改变,就有可能会发生fail-fast,
      即抛出ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制通常用来检测bug。在ArrayList源码中有一定的描述。

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 {@code 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.

制造异常

public class Main {
    public static void main(String[] args) {
        testSingleThread();
    }

    //测试单线程的情况下的异常
    public static void testSingleThread() {
        System.out.println("testSingleThread()");
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(i + "");
        }
        //创建一个迭代器
        Iterator<String> iterator = list.iterator();
        int i = 0;
        while (iterator.hasNext()) {
            if (i == 3) {
                list.remove(3); //不安全的做法
               // iterator.remove();
            }
            System.out.print(iterator.next() + " ");
            i++;
        }
    }

    //测试多线程的情况下
    public static void testConcurrent() {
        System.out.println(" testConcurrent()()");
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(i + "");
        }
        Iterator<String> iterator = list.iterator();
        //线程池创建10个线程
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //执行线程
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    while(iterator.hasNext()) {
                        System.err.print(iterator.next() + " ");
                    }
                   list.remove(2);
                }
            });
        }
        executorService.shutdown();
    }
}

运行结果如下

testSingleThread()
Exception in thread “main” java.util.ConcurrentModificationException
0 1 2 at java.util.ArrayListI t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 901 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayListItr.checkForComodification(ArrayList.java:901)atjava.util.ArrayListItr.next(ArrayList.java:851)
at com.demo.list.Main.testSingleThread(Main.java:33)
at com.demo.list.Main.main(Main.java:15)

testConcurrent()
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 16 18 19 20 22 23 24 25 26 27 28 29 21 31 32 33 34 35 36 37 38 39 40 41 30 42 44 43 45 46 47 48 49 51 50 52 54 55 56 57 58 59 60 53 61 63 62 65 66 67 68 64 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 93 94 95 96 97 98 99 92
Exception in thread “pool-1-thread-2” java.util.ConcurrentModificationException
at java.util.ArrayListI t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 901 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayListItr.checkForComodification(ArrayList.java:901)atjava.util.ArrayListItr.next(ArrayList.java:851)
at com.demo.list.Main1. r u n ( M a i n . j a v a : 54 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r . r u n W o r k e r ( T h r e a d P o o l E x e c u t o r . j a v a : 1142 ) a t j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r 1.run(Main.java:54) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor1.run(Main.java:54)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)atjava.util.concurrent.ThreadPoolExecutorWorker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)

上面可以看出,在迭代的过程中调用list.remove(i)的方法就会出现ConcurrentModificationException

源码溯源

接下来就根据源码来查找出现问题的原因
ArrayList中的迭代方法
 public Iterator<E> iterator() {
        return new Itr();
    }
    
  /**
     *内部定义了Itr的内部类来实现迭代器
     * An optimized version of AbstractList.Itr
     */
    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最后一个元素的索引,没有就为-1
        int expectedModCount = modCount; //期待的修改的次数

        public boolean hasNext() { //当cursor 不等于 元素的个数(size),说明还没  到最后
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() { //这个就是拿到当索引的值
            checkForComodification(); //检查modCount,看modCount是不是已经被别的线程修改了
            int i = cursor; //i为当前值的索引
            if (i >= size) //当索引大于size就抛异常
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;//拿到值数组
            if (i >= elementData.length) //当索引大于数组长度也直接抛异常,  
            //这是针对size 与 elementData不同步做的检查
                throw new ConcurrentModificationException();
            cursor = i + 1; //游标+1
            return (E) elementData[lastRet = i];//返回值,且将i赋给lastRet
        }

        public void remove() { //请记住,这是ArrayList中iterator的remove方法  
       //不是ArrayList中的remove方法,虽然都是来实现删除操作的,但是底层实现不相同
            if (lastRet < 0) //最后一个下标是小于0,直接抛参数异常
                throw new IllegalStateException();
            checkForComodification(); //检查modCount

            try {
                ArrayList.this.remove(lastRet); //l根据astRet进行删除
                cursor = lastRet; //cursor 也变为 lastRet,因为删除了当前位置的值
                lastRet = -1; 
                expectedModCount = modCount; //得到ModCount
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        //检查modCout是否被其他线程修改
        final void checkForComodification() {
            if (modCount != expectedModCount) //说明modCount已经发生了修改,ArrayList的结构已经发生改变
                throw new ConcurrentModificationException();//直接抛异常
        }
    }

我们再来看看ArrayList中的remove()方法

public E remove(int index) {
        rangeCheck(index); //检查index是否合法

        modCount++; //我去,modCout直接++了,这也就说明为啥terator.next()会报错
        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;
    }

再次看看出现异常的代码

 public static void testSingleThread() {
        System.out.println("testSingleThread()");
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(i + "");
        }
        //创建一个迭代器
        Iterator<String> iterator = list.iterator();
        int i = 0;
        while (iterator.hasNext()) {
            if (i == 3) {
                list.remove(3); //不安全的做法,这里调用的是ArrayList的remove方法,由于remove,modCount++;
               // iterator.remove();//这才是最正确的做法
            }
            System.out.print(iterator.next() + " ");//恰好在这里的expectedModCount没有修改,checkForComodification就直接抛异常
            i++;
        }
    }

在高并发环境下也是如此,迭代的时候,其他线程修改了结构,就会抛异常

总结

  其实就是集合中的错误检查机制,Map,List中都存在.
  1.在迭代删除值的时候,要使用iterator,remove()方法,原因就是这modCount != expectedModCount
  2.在高并发环境下,推荐使用CopyOnWriteArrayList,ConcurrentHashMap

文章到此,我是一个出生牛肚不怕虎的99年小伙子,有什么问题大家可以评论区留言,谢谢大家的阅读!!!


版权声明:本文为qq_41919300原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。