资讯专栏INFORMATION COLUMN

快速失败(fail-fast)与安全失败(fail-safe)

imtianx / 1039人阅读

摘要:注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出。

fail-fast与fail-safe

在Collection集合的各个类中,有线程安全和线程不安全这2大类的版本。

对于线程不安全的类,并发情况下可能会出现fail-fast情况;而线程安全的类,可能出现fail-safe的情况。

一、并发修改

当一个或多个线程正在遍历一个集合Collection的时候(Iterator遍历),而此时另一个线程修改了这个集合的内容(如添加,删除或者修改)。这就是并发修改的情况。

二、fail-fast快速失败

fail-fast机制:当遍历一个集合对象时,如果集合对象的结构被修改了,就会抛出ConcurrentModificationExcetion异常。

有2种情况会抛出该异常:

在单线程的情况下,如果使用Iterator对象遍历集合对象的过程中,修改了集合对象的结构。如下:

// 1.iterator迭代,抛出ConcurrentModificationException异常
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
  String s = iterator.next();
  System.out.println(s);
  // 修改集合结构
  if ("s2".equals(s)) {
    list.remove(s);
  }
}

// 2.foreach迭代,抛出ConcurrentModificationException异常
for (String s : list) {
  System.out.println(s);
  // 修改集合结构
  if ("s2".equals(s)) {
    list.remove(s);
  }
}

要想避免抛出异常,应该使用Iterator对象的remove()方法。

// 3.iterator迭代,使用iterator.remove()移除元素不会抛出异常
Iterator iterator2 = list.iterator();
while (iterator2.hasNext()) {
  String s = iterator2.next();
  System.out.println(s);
  // 修改集合结构
  if ("s2".equals(s)) {
  iterator2.remove();
  }
}

在多线程环境下,如果对集合对象进行并发修改,那么就会抛出ConcurrentModificationException异常。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法,迭代器的快速失败行为应该仅用于检测 bug。

以ArrayList为例,讲解一下fail-fast的机制 1、单线程下,使用iterator迭代时的情况

ArrayList继承自AbstractList类,AbstractList内部有一个字段modCount,代表修改的次数。

ArrayList类的add、remove操作都会使得modCount自增。

当使用ArrayList.iterator()返回一个迭代器对象时。迭代器对象有一个属性expectedModCount,它被赋值为该方法调用时modCount的值。这意味着,这个值是modCount在这个时间点的快照值,expectedModCount值在iterator对象内部不会再发送变化!

这时候我们就能明白了,在得到迭代器之后,如果我们使用ArrayList的add、remove等方法,会使得modCount的值自增(发生了变化),而iterator内部的expectedModCount值却还是之前的快照值。我们再来看iterator的方法实现:可以看到,在调用next方法时,第一步就是检查modCount值和迭代器内部的expectedModCount值是否相等!显然,这是不等的,所以在调用next方法的时候,就抛出了ConcurrentModificationException异常。

为什么说迭代器的fail-fast机制是尽最大努力地抛出ConcurrentModificationException异常呢?

原因就是上面我们看到的,只有在迭代过程中修改了元素的结构,当再调用next()方法时才会抛出该异常。也就是说,如果迭代过程中发生了修改,但之后没有调用next()迭代,该异常就不会抛出了!(该异常的机制是告诉你,当前迭代器要进行操作是有问题的,因为集合对象现在的状态发生了改变!)

那为什么iterator.remove()方法可行呢?

下图中,可以看到,remove方法没有进行modCount值的检查,并且手动把expectedModCount值修改成了modCount值,这又保证了下一次迭代的正确。

2、多线程下的情况

当然,如果多线程下使用迭代器也会抛出ConcurrentModificationException异常。而如果不进行迭代遍历,而是并发修改集合类,则可能会出现其他的异常如数组越界异常。

三、fail-safe安全失败

Fail-Safe 迭代的出现,是为了解决fail-fast抛出异常处理不方便的情况。fail-safe是针对线程安全的集合类。

上面的fail-fast发生时,程序会抛出异常,而fail-safe是一个概念,并发容器的并发修改不会抛出异常,这和其实现有关。并发容器的iterate方法返回的iterator对象,内部都是保存了该集合对象的一个快照副本,并且没有modCount等数值做检查。如下图,这也造成了并发容器的iterator读取的数据是某个时间点的快照版本。你可以并发读取,不会抛出异常,但是不保证你遍历读取的值和当前集合对象的状态是一致的!这就是安全失败的含义。

所以Fail-Safe 迭代的缺点是:首先是iterator不能保证返回集合更新后的数据,因为其工作在集合克隆上,而非集合本身。其次,创建集合拷贝需要相应的开销,包括时间和内存。

在java.util.concurrent 包中集合的迭代器,如 ConcurrentHashMap, CopyOnWriteArrayList等默认为都是Fail-Safe。

// 1.foreach迭代,fail-safe,不会抛出异常
for (String s : list) {
  System.out.println(s);
  if ("s1".equals(s)) {
  list.remove(s);
  }
}

// 2.iterator迭代,fail-safe,不会抛出异常
Iterator iterator = list.iterator();
  while (iterator.hasNext()) {
  String s = iterator.next();
  System.out.println(s);
  if ("s1".equals(s)) {
  list.remove(s);
  }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71683.html

相关文章

  • fail-fastfail-safe

    摘要:一什么是机制在用迭代器遍历集合时当集合的结构被修改会抛出异常二什么情况下集合的结构会被修改单线程环境集合在遍历的过程中如果要对集合进行增删操作没有调用迭代器的方法而是用的集合自身的方法则可能会产生事件多线程环境下当一个线程在遍历某个集合 一.什么是fail-fast机制? 在用迭代器遍历集合时,当集合的结构被修改,会抛出ConcurrentModificationException异常...

    魏明 评论0 收藏0
  • 面试官:说说快速失败安全失败是什么

    摘要:我们都接触这些集合类,这些在包的集合类就都是快速失败的而包下的类都是安全失败,比如。安全失败明白了什么是快速失败之后,安全失败也是非常好理解的。最后说明一下,快速失败和安全失败是对迭代器而言的。 什么是快速失败(fail-fast)和安全失败(fail-safe)?它们又和什么内容有关系。以上两点就是这篇文章的内容,废话不多话,正文请慢用。 我们都接触 HashMap、ArrayLis...

    calx 评论0 收藏0
  • fail-fastfail-safe在Java集合中的应用

    摘要:与在迭代器中的设计在中,最典型的与就是关于迭代器的设计。缺点是,迭代器不能正确及时的反应集合中的内容,而且一定程度上也增加了内存的消耗。 fail-fast与fail-safe简介 如果一个系统,当有异常或者错误发生时就立即中断执行,这种设计称之为fail-fast。相反如果我们的系统可以在某种异常或者错误发生时继续执行,不会被中断,这种设计称之为fail-safe。 fail-fas...

    Drummor 评论0 收藏0
  • 带你了解集合世界的fail-fast机制 和 CopyOnWriteArrayList 源码详解

    摘要:体现的就是适配器模式。数组对象集合世界中的机制机制集合世界中比较常见的错误检测机制,防止在对集合进行遍历过程当中,出现意料之外的修改,会通过异常暴力的反应出来。而在增强循环中,集合遍历是通过进行的。 前言 学习情况记录 时间:week 2 SMART子目标 :Java 容器 记录在学习Java容器 知识点中,关于List的重点知识点。 知识点概览: 容器中的设计模式 从Array...

    young.li 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<