单线程是否会引起 fail-fast机制

描述

fail-fast 是什么

引用百度百科的数据:

fail-fast 机制是 java 集合 (Collection) 中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程 A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件。

多线程?并发修改?才会引起 fail-fast 机制保护程序?小 B 觉得这个答案没有说全,面试官说了单线程也会引起 fail-fast 机制。那么关于单线程是否会引起 fail-fast 机制,百度百科说的对还是面试官说的对,写一个 demo 就清楚了。

import java.util.ArrayList;
import java.util.List;

public class FastFailTest {

    public static void main(String[] args) {
        List< String > list = new ArrayList< String >();

        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");

        for(String s : list) {
            if(s.equals("赵六")) {
                list.remove(s);
                System.out.println(list.toString());
            }
        }
    }
}

从下图的运行结果来看,list 已经完成了对 [赵六] 的 remove,说明并不是 remove 引发的问题,仔细查看异常原因:是在 ArrayList 的内部 Itr.checkForComodification() 方法出现的 ConcurrentModificationException 异常。小 B 感概了一句:网上资料不可尽信,动手实战才能出真知。

单线程

原理

将异常定位到报错的 ArrayList.java:911 行。

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

可以看到这个方法 checkForComodification 对 modCount 和 expectedModCount 进行了比较,如果不相同就抛出异常。modCount 和 expectedModCount 分别又是什么呢?remove 方法中不是修改了 modCount 就是修改了 expectedModCount。

modCount 被定义在 ArrayList 的父类 AbstractList 中,每一次调用 Add、Remove、Clear 等方法 modCount 就被 +1,可以说明这个变量的作用就是记录了 ArrayList 实际被修改的次数。

ArrayList 的 foreach 方法是用迭代器 Iterator 实现的,Iterator 在 ArrayList 中有一个实现类:Itr,它的成员变量 expectedModCount 在初始化的时候被赋值了 modCount。所以当 ArrayList 调用 remove 删除元素时,modCount 被 +1,此时不等于 expectedModCount,在 foreach 试图将局部变量 s 交接给下一个元素的时候,就出现了 ConcurrentModificationException 异常。

单线程

避免

经过分析,ConcurrentModificationException 是由于 modCount 和 expectedModCount 不一样导致的。

那么如何避免在循环的时候 add、remove 元素不抛出异常呢?

for 循环

使用普通的 for 循环,这样就可以不经过 Itr 内部类了。

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

list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

for( int i = 0; i < list.size(); i++) {
    String s = list.get(i);
    if(s.equals("王五")) {
        list.remove(s);
        System.out.println(list.toString());
    }
}

示例结果是 [张三, 李四, 赵六] 没有出现异常。但是移除元素后面的索引已经被改变了。

迭代器 Iterator

直接使用迭代器 Iterator 中的方法,在它的remove 方法中显示的将 expectedModCount 赋值成 modCount。

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

定义一个迭代器局部变量,使用 hasNext() 方法控制 while 循环。

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

list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

Iterator< String > iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if(s.equals("王五")) {
        iterator.remove();
        System.out.println(list.toString());
    }
}

CopyOnWriteArrayList

CopyOnWriteArrayList 是 java 并发包 java.util.concurrent 下面的类。它在操作 add、remove 元素时,先将原来的元素数组拷贝一份成为新的数组,在新数组上面做元素操作,修改完成后,将 CopyOnWriteArrayList 中数组的引用指向了新数组。

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

list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

for(String s : list) {
    if(s.equals("李四")) {
        list.remove(s);
        System.out.println(list.toString());
    }
}

总结

fail-fast 机制就是不允许程序员不管是在单线程还是多线程环境中遍历集合的时候顺便还操作集合里面的元素。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分