Java集合中的fail-fast机制

2018-04-26  本文已影响27人  8813d76fee36

fail-fast简介

在java.util包下的集合类中,有一个成员变量modCount。

protected transient int modCount = 0;

这个变量的数值表示使集合发生结构变化的次数。
引起结构变化的操作指诸如影响集合大小(新增、删除元素)或干扰遍历结果的操作。

原理简述

当调用会改变集合结构的操作,如添加、删除元素时,会使变量modCount的数值增加1,以此来记录当前集合的结构被修改了多少次。迭代器被实例话的时候,其内部也会拷贝一份该数值,存入迭代器成员变量expectedModCount中。在迭代器操作集合(如netx()、remove() )之前会校验expectedModCount与集合中的modCount值是否一致,若不一致,则说明在遍历过程中,集合的结构发生了变化,此时迭代器会立即终止对集合的操作,抛出ConcurrentModificationException异常。

实现机制解析

我们以ArrayList为例,从源码入手看看这个机制是如何工作的。

        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < 5; i ++) {
            list.add(i);
        }

在add()方法中我们可以看到modCount累加的操作。

private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //操作数自增一

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

由于我们使用循环依次添加了5个元素,也就是调用了5次add方法,因此结构应该发生了5次变化,此时modCount的数值应该为5。


modCount
        for (Integer num : list) {
            if (num % 2 == 0) {
                list.remove(num);
            }
        }

事实证明,该代码在执行时会抛出异常,而该异常正是ConcurrentModificationException异常。

我们来看看上面这段代码发生了什么。

迭代器

如图可见,for-each调用了ArrayList的迭代器,此时迭代器的expectedModCount已经被modCount赋值为5。
当调用next()方法获取元素时,首选会调用checkForComodification()方法检查集合结构是否被修改。


checkForComodification

而检查的依据就是判断expectedModCount和modCount的值是否相等。


checkForComodification内部
由于现在是遍历操作,不会引起集合结构变化,所以modCount值没有改变,第一次遍历检查通过,成功拿到第一个元素0,并且由于0是偶数,因此它被成功删除。
0已经不见了
继续执行遍历,由于刚才执行了remove()方法,list的结构发生变化,modCount也随之自增,此时expectedCount已经和modCount不同了。
modCount发生改变

这时迭代器的checkForComodification方法检测到两个数值不一致,抛出了ConcurrentModificationException异常。


ConcurrentModificationException
Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            if (iterator.next() % 2 == 0) {
                iterator.remove();
            }
        }
迭代器remove

此时没有触发ConcurrentModificationException异常是因为迭代器的remove()方法中,在删除元素后,将expectedModCount的值做了同步。
但在并发环境下解决这个问题还应该使用java.util.current包下的集合类。该包下的集合类在修改集合结构时没有在原集合直接修改,而是将数据复制到一个新数组内,在新数组内修改后再将值同步到原数组中。

下面是java.util.current.CopyOnWriteArrayList类的说明

 * A thread-safe variant of {@link java.util.ArrayList} in which all mutative
 * operations ({@code add}, {@code set}, and so on) are implemented by
 * making a fresh copy of the underlying array.
上一篇 下一篇

猜你喜欢

热点阅读