Java容器系列-Fail-Fast机制究竟是什么

fail-fast 其实是一种系统设计的方法,维基百科上是这样解释的:java

在系统设计中,一个 fail-fast 的系统能够经过特定的接口快速报告系统中任何潜在的故障。fail-fast 系统在发现故障时不会尝试继续运行系统,而会当即中止当前的操做。在进行一个操做时,会在多个检查点检查系统的状态,因此出现故障能够被尽早发现。fail-fast 模块的职责就是检查是否有故障,发现故障后会尽快通知上层系统来处理故障。编程

上面的文字看起来有点晦涩,实际的意思就是 fail-fast 是一种快速发现系统故障的机制,在检测到系统状态不对时,会当即中止当前的操做,让上层的系统来处理这些故障。安全

与 fail-fast 相对的是 fail-safe。顾名思义,fail-safe 在故障发生以后会维持系统继续运行。Java 在容器中用到了这两种机制。微信

当使用迭代器(iterator)遍历容器时,迭代器分为两种状况,一种是 fail-fast,另外一种是 fail-sale。并发

fail-fast 在遍历时,若是容器的元素被修改,就会报 ConcurrentModificationException 异常,而后终止遍历。源码分析

fail-safe 意味着在遍历元素时,即便容器的元素被修改,不会抛出异常,也不会中止遍历。post

本文基于 JDK1.8this

fail-fast 具体实现

看以下的代码:spa

ArrayList<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
    Integer a = itr.next();
    integers.remove(a);
}
复制代码

上面使用 ArrayList 的代码会报 ConcurrentModificationException 异常。线程

List<Integer> integers = new CopyOnWriteArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
    Integer a = itr.next();
    integers.remove(a);
}
复制代码

而使用 CopyOnWriteArrayList 的代码则不会报异常。

fail-fast 机制和 modCount 这个变量有关,这个变量会记录容器被修改的次数,能够理解为容器对象的版本号。

那么容器怎样才算是被修改呢?

  • 当容器元素被删除
  • 当容器增长一个元素
  • 当容器中的元素执行了排序操做
  • 当容器被其余容器对象替代

须要注意,修改容器中元素的内容 modCount 不会增长

当容器使用迭代器对元素进行迭代时,会把 modCount 赋值给 expectedModCount。

int expectedModCount = modCount;
复制代码

在迭代的过程当中,会不断的去检查 expectedModCount 与 modCount 的值是否相等,若是不相等,就说明在容器迭代的过程当中,有其余的操做修改了容器,致使 modCount 的值增长,那么就会报 ConcurrentModificationException 异常。

注:fail-fast 机制不单单在迭代器中使用了,容器的增删改查和序列化等操做中也用到了。

有没有办法在迭代的过程当中删除某些元素?使用迭代器自己的 remove 方法就行,这样不会产生异常:

Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
    if (itr.next() == 2) {
        itr.remove();
    }
}
复制代码

不会产生异常的缘由是在删除元素后,把最新的 modCount 的值赋值给了 expectedModCount,代码以下:

// ListItr.remove() method
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
复制代码

fail-fast 机制能够用来检测容器元素是否被修改,可是须要注意的是,不能依赖 fail-fast 机制来保证容器的元素不被修改,也就是说,不要在没有同步的状况下并发的修改容器中的元素。fast-fast 机制原本的职责就是检测系统的错误,因此仅仅只用它来检测 bug,而不要做其余的用途。

注:同步是并发编程中的一个术语,若是说一段代码是同步的,那就表明是线程安全的

fail-safe 具体实现

和 fail-fast 不一样的是,使用了 fail-safe 的容器则能够在迭代的过程当中任意的修改容器的元素而不会报错。本质是由于迭代的是容器元素的副本,也就是说是将容器的元素拷贝了一份再进行遍历,这样即便原容器被修改,也不会影响到当前正在遍历的元素。

CopyOnWriteArrayList 是一个支持 fail-safe 的容器,它获取迭代器的代码以下:

// CopyOnWriteArrayList.listIterator() method
Object[] es = getArray();
int len = es.length;
if (index < 0 || index > len)
    throw new IndexOutOfBoundsException(outOfBounds(index, len));
return new COWIterator<E>(es, index);
复制代码
// COWIterator inner class
private final Object[] snapshot;
private int cursor;

COWIterator(Object[] es, int initialCursor) {
    cursor = initialCursor;
    snapshot = es;
}
复制代码

COWIterator 将容器的元素作了一个快照,后续的操做都是在这个快照上进行的。

为了支持 fail-safe 特性,须要付出额外的代价:

  • 迭代器中的元素不是容器的最新状态
  • 须要额外的内存或者时间上的开销

java.util 包下的容器都有 fail-fast 机制,而java.util.concurrent 包下的容器则都是 fail-safe 的。

REF:

(完)

原文

相关文章

关注微信公众号,聊点其余的

相关文章
相关标签/搜索