java中fail-fast机制详解

java中fail-fast机制详解

最近遇到一个HashMap相关的问题:如果HashMap在扩容的时候插入一组元素,这组元素能够插入成功吗?于是想到了fail-fast机制,以下是对于fail-fast的一些分析。


以ArrayList的源码为例:

public static void main(String[] args) {

        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);
            }
            System.out.println(iterator.next());
            i++;
        }
    }

打印的结果为以下,会报出ConcurrentModificationException的异常

0
1
2
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at generic.Generic.main(Generic.java:35)

ArrayList中的list.iterator()方法:

/**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new Itr();//此处调用下面的内部类Itr
    }

    /**
     * An optimized version of AbstractList.Itr
     */
     //该内部类实现了Iterator接口,所以使得ArrayList可迭代
    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
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

分析Itr类的next()方法可以发现,在最开始的地方调用了checkForComodification()方法,此方法的作用就是来判断是List是否改变,源码如下:

//modCount用于记录集合操作过程中作的修改次数
//expectedCount指的是期望的modCount,初始化的时候这两个值是相等的
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

如果modCount不等于expectedModCount那么就会抛出ConcurrentModificationException异常,那么什么时候modCount会改变?
当执行add方法的时候,会调用ensureCapacityInternal,该方法会将modCount++,同理,在调用remove,clear方法的时候modCount++。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
 /**
 *下面是Itr类中的remove方法
 *每一次remove执行,都会把modCount与expectModCount变为一致
 **/
 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();
            }
        }

此时需要注意,我们调用的是ArrayList中的add,clear,remove方法,因为ArrayList还有个内部类Itr,其中也有这几个方法,当Itr中方法的时候,modCount与expectModCount一直是一致的,并且不会改变。
所以,因为每一次执行next方法的时候都会先调用方法判断两个Count是否一致,所以当执行list.remove()方法后,modCount+1,而expectModCount并没有改变,所以就会抛出ConcurrentModificationException异常。


如何避免fail-fast呢?

1.使用迭代器提供的remove,add,clear等方法,如上面分析,该remove方法并不会修改modCount的值,并且不会对后面的遍历造成影响。
2.使用java并发包(java.util.concurrent)中的类来代替ArrayList 和hashMap。
比如使用 CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList几乎一样,CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
对于HashMap,可以使用ConcurrentHashMap,ConcurrentHashMap采用了锁机制,是线程安全的。在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。


回到最初的问题,如果HashMap扩容过程中有元素插入,会发生什么?
(源码看来看去,在扩容方法执行中并没有检验modCount是否等于expectModCount,所以,个人猜测,这个插入的元素有可能会被漏掉,也有可能会正常插入)
如果调用HashMap中的迭代器,那么会触发fail-fast机制(hashMap中有KeySpliterator,ValueSpliterator,EntrySpliterator)

Map<String,String> map= new HashMap<String,String>();  
        map.put("test", "test");  


        Iterator iterator = map.keySet().iterator();  

        while (iterator.hasNext())  
        {  
            map.put("test1", "test1");  
        }  

参考链接:
http://blog.csdn.net/zymx14/article/details/78394464

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值