【关于集合类中的fail-fast详细说明】

关于集合类中的fail-fast和fail-safe详细说明

  • 集合类中的fail-fast
    • 异常原理

fail-fast和fail-safe的概念

集合类中的fail-fast

我们通常说的Java中的fail-fast机制,默认的话指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException。

ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时就会抛出该异常信息。

在Java中,如果在foreach循环里对某些集合元素进行元素的remove/add操作的时候,就会触发fail-fast机制,进而抛出ConcurrentModificationException。

如以下代码:

List<String> userNames = new ArrayList<String>() {{
    add("yang");
    add("xiao");
    add("yuan");
    add("Y");
}};

for(String userName : userNames) {
    if(userName.equals("yang")) {
        userNames.remove(userName);
    }
}

System.out.println(userNames);

以上代码,使用增强版for循环遍历集合元素,并尝试删除其中的yang字符串元素。运行这段代码,就会抛出异常。如图:
异常信息1
同样的,大家可以尝试一下在增强for循环中使用add方法添加元素,结果会抛出同样的异常信息。
我们在做深入理解之前,先尝试把foreach进行解语法糖,看一下foreach具体是怎么实现的。
我们使用jad工具,对编译后的class文件进行反编译,得到如下代码:

public static void main(String args[]) {
    //说明:使用ImmutableList初始化一个List
    List<String> userNames = new ArrayList<String>() {{
        add("yang");
        add("xiao");
        add("yuan");
        add("Y");
    }};
    Iterator iterator = new userNames.iterator();
    //do……while
    do
        {
            if(!iterator.hashNext())
                break;
                //这里做一下显示转换
            String userName = (String)iterator.next();
            if(userName.equals("yang"))
                userNames.remove(userName);
        }while(true);
        System.out.println(userNames);
}

可以发现,foreach其实是依赖了while循环和Iterator实现的。

异常原理

通过以上代码的异常堆栈,我们可以跟踪到真正抛出异常的代码是哪里:
2
该方法是在iterator.next()方法中调用的,我们看下这个方法的实现:

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

正如上面,在该方法中对modCountexpectedModCount进行了比较,如果二者不相等,则抛出ConcurrentModificationException。
那么,modCountexpectedModCount是什么呢?是什么原因导致他们的值不相等呢?
带着问题找答案!modCount是ArrayList中的一个成员变量。它本身表示该集合实际被修改的次数。

List<String> userNames = new ArrayList<String>() {{
    add("yang");
    add("xiao");
    add("yuan");
    add("Y");
}};

当使用以上代码初始化集合之后该变量是不是就没有了,没有了初始值就是0。

expectedModCount是ArrayList中的一个内部类 ———Itr中的成员变量。

Iterator iterator = new userNames.iterator();

以上代码,即可得到一个Itr类,该类实现了Iterator接口。

expectedModCount表示这个迭代器预期该集合被修改的次数,他的值随着Itr被创建而初始化。只有通过迭代器对集合进行操作,这个值才会被改变。(重点)
那么,接下来看一下userNames.remove(userName);方法里面做了什么事情,为什么导致expectedModCount和modCount的值不一样。

通过我不断地翻阅源码,发现,remove方法核心逻辑,代码如下:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index -1;
    if(numMoved >0) {
          System.arraycopy(elementData,index+1,elementData,index,numMoved);
          elementData[--size] = null;//clear to let GC do its work
    }
}

可以看到,它只修改了modCount,并没有对expectedModCount做任何操作。
要是感觉还是很难理解的话,那我就大家简单画一个图吧。
【关于集合类中的fail-fast详细说明】_第1张图片
简单总结一下吧!之所以会抛出CMException异常,是因为我们的代码中使用了增强办的for循环,而在增强版的for循环中,集合遍历是通过iterator进行的,但是元素的add/remove确是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被add/remove掉了,就会抛出一个异常,用来提示用户,可能发生了并发修改!
所以呢,在使用Java的集合类的时候,如果发生CMException,优先考虑fail-fast机制的有关的情况,实际上这里并没有真正的发生并发,只是Iterator使用了fail-fast的保护机制,就是只要它发现有某一次修改是未经过自己进行的,那他就会吃醋,抛出异常。

Ok,这是对集合类中的fail-fast的详细说明,下一篇博文是fail-safe的详细说明,记得订阅哦!

你可能感兴趣的:(Java集合类,开发语言,java,算法)