(简洁版)Java的快速失败(fail-fast)与安全失败,源码分析+详细讲解

一、什么是快速失败,什么是安全失败?

1.1 快速失败,是java集合(Collection)中的一种错误检测机制。

1.2 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

二、出现场景(单线程、多线程都可能出现)

2.1 单线程

public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        //为集合添加100个数
        for (int i = 0; i < 100; i++) {
            list.add(i + "");
        }
        //获取集合的迭代器
        Iterator<String> iter = list.iterator();
        //我们在集合中删除一个数据,注意我们是直接用集合操作而不是迭代器
        list.remove("3");
        //再使用迭代器获取数据
        iter.next();
    }

再来看看ArrayList的源码

public class ArrayList<E> ,,,,,

{

....

//modCount:集合的修改次数

protected transient int modCount = 0;

//当出现增删操作时,便会modCount++;

public E remove(int index) {

        ....

        modCount++;

        ......

    }

....

//通过此方法获取迭代器

public Iterator<E> iterator() {

return new Itr();

}

....

//迭代器是ArrayList的一个内部类

private class Itr implements Iterator<E> {

        ...

//每次获取一个迭代器,就新建一个对象,将expectedModCount赋值为modCount

        int expectedModCount = modCount;

       .....

        @SuppressWarnings("unchecked")

        public E next() {

//检查expectedModCount和modCount是否相等

            checkForComodification();

            .....

        }

//这个就是检查的方法

final void checkForComodification() {

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

        }  

.....

  }

那么上面报错也就不难解释了:

假设创建一个迭代器时modCount=10,那么expectedModCount=10(注意,modCount是ArrayList的参数,而expectedModCount是迭代器的参数,从创建迭代器之后,他们就没联系了)

当执行list.remove("3");操作时,modCount++=11;而此时expectedModCount=10;当我们再执行iter.next()时,很明显两个数不一致,抛出异常。

2.2 多线程

public class test {
        private static List<String> list = new ArrayList<String>();

        public static void main(String[] args) {
            for (int i = 0; i < 100; i++)
                list.add(i + "");
            System.out.println("线程开始了");
            new threadOne().start();
            new threadTwo().start();
        }

        //线程1每隔10ms遍历一次
        private static class threadOne extends Thread {
            public void run() {
                Iterator<String> iter = list.iterator();
                while (iter.hasNext()) {
                    System.out.println("线程1遍历到了:" + iter.next());
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        //线程2每隔10ms删除一个
        private static class threadTwo extends Thread {
            public void run() {
                Iterator<String> iter = list.iterator();
                while (iter.hasNext()) {
                    System.out.println("线程2删除了:" + iter.next());
                    iter.remove();
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

看看迭代器的remove()操作

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

线程1删除时执行到语句1,modCount+1。

由于语句2还没执行,expectedModCount不会改变

这时处理器跳转到线程2执行。

线程2执行next()时,对比modCount和expectedModCount。

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

发现两值不一致,抛出ConcurrentModificationException

2.3 结论

无论是单线程/多线程下的add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值。类似的,hashMap中发生的原理也是一样的。

三、如何避免fail-fast

3.1 调用迭代器的remove方法

在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法。

但是该方法remove不能指定元素,只能remove当前遍历过的那个元素,这也是该方法的局限性。

3.2 使用“java.util.concurrent包下的类”去取代“java.util包下的类

private static List<String> list = new CopyOnWriteArrayList<String>();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值