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
看以下的代码: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-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:
(完)
相关文章
关注微信公众号,聊点其余的