007Java集合012快速失败机制和安全失败机制

注意:本文基于JDK1.8进行记录。

1 快速失败机制

1.1 说明

快速失败机制,即fail-fast机制,直接在容器上进行遍历,在遍历过程中一旦发现集合的结构发生改变,就会抛出ConcurrentModificationException异常导致遍历失败。

java.util包下的集合类都是快速失败机制的,常见的的使用fail-fast方式遍历的容器有ArrayList和HashMap等。

fail-fast机制不能保证在不同步的修改下一定会抛出异常,它只是尽最大努力抛出,因此这种机制一般用于检测BUG。

1.2 现象

触发fail-fast机制的案例:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("00");
    list.add("11");
    list.add("22");
    for (String s : list) {
        if ("00".equals(s)) {
            list.add("99");
        }
    }
}

注意:此处只是列举了一个普通案例,实际上在List、Set、Map中都会发生,并且在单线程和多线程环境下都会发生。

1.3 原因

fail-fast机制在单线程和多线程环境中均可发生,倘若在迭代遍历过程中检测到集合结构有变化,就有可能触发并抛出异常。

想要理解fail-fast机制,就需要查看底层源码的逻辑,因为引发fail-fast机制的原理是一样的,本文以ArrayList为例进行分析。

查看ArrayList的迭代器方法:

public Iterator<E> iterator() {
    return new Itr();
}

继续查看ArrayList维护的内部类Itr,需要重点关注三个属性:

private class Itr implements Iterator<E> {
    int cursor;       // 遍历集合时即将遍历的索引
    int lastRet = -1; // 记录刚刚遍历的索引,-1不是不存在上一个元素
    int expectedModCount = modCount;// 初始值为modCount,用于记录集合的修改次数

    public boolean hasNext() {
        return cursor != size;// 判断遍历是否结束
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();// 检查是否触发fail-fast机制
        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];
    }
    ...
    final void checkForComodification() {
        if (modCount != expectedModCount)
            // expectedModCount在初始化后并未发生改变,那么如果modCount发生改变,就会抛出异常
            throw new ConcurrentModificationException();
    }
}

通过分析迭代器源码可以发现,迭代器的checkForComodification方法是判断是否要触发fail-fast机制的关键。

在checkForComodification方法中可以看到,是否要抛出异常在于modCount是否发生改变。

查看ArrayList源码,发现modCount的改变发生在对集合修改中,比如add操作。

所以当在使用迭代器遍历集合时,如果同时对集合进行了修改,导致modCount发生改变,就会触发fail-fast机制,抛出异常。

1.4 解决

1.4.1 使用迭代器提供的方法

为了避免触发fail-fast机制,在迭代集合时,需要使用迭代器提供的修改方法修改集合。

1.4.2 使用线程安全的集合类

也可以使用线程安全的集合类,使用CopyOnWriteArrayList代替ArrayList,使用ConcerrentHashMap代替HashMap。

2 安全失败机制

2.1 说明

安全失败机制,即fail-safe机制,在集合的克隆对象上进行遍历,对集合的修改不会影响遍历操作。

java.util.concurrent包下的集合类都是安全失败的,可以在多线程下并发使用并发修改,常见的的使用fail-safe方式遍历的容器有CopyOnWriteArrayList和ConcerrentHashMap等。

基于克隆对象的遍历避免了在修改集合时抛出ConcurrentModificationException异常,但同样导致遍历时不能访问修改后的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值