一提到fail-fast几乎都是java集合里面的Iterator,确实java集合里面的迭代器用到了fail-fast机制,但是fail-fast机制不局限于此 看看维基百科的解释
In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them. 在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作, 而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。 快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。 复制代码
说白了就是优先考虑异常,发现异常直接停止上报。
BigDecimal类下这个divide方法,在最开始就预判了除数为0的情况,并且直接抛出异常,这就是最好的fail-fast机制的应用。
public BigDecimal divide(BigDecimal divisor) { /* * Handle zero cases first. */ if (divisor.signum() == 0) { // x/0 if (this.signum() == 0) // 0/0 throw new ArithmeticException("Division undefined"); // NaN throw new ArithmeticException("Division by zero"); } //省略其他逻辑... ... } 复制代码
提到java集合类中fail-fast机制,大概就是抛出ConcurrentModificationException这个异常了。 这里拿ArrayList的iterator()的源码说明一下
1、迭代器iterator()方法执行的时候int expectedModCount = modCount, 2、modCount是AbstractList定义的 protected transient int modCount = 0; 3、迭代器在调用 next()、remove() 方法时都是调用 checkForComodification() 方法, 该方法主要就是检测 modCount == expectedModCount ? 4、ArrayList 中无论 add、remove、clear 方法只要是涉及了改变 ArrayList 元素的个数的方法都会导致 modCount 的改变 复制代码
部分源码
public Iterator<E> iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount;//调用iterator()的时候赋值 Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } 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(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } 复制代码
java.util.concurrent包下的容器都是fail-safe 对应ArrayList的类CopyOnWriteArrayList CopyOnWriteArrayList这个类设计思想是读写分离,写(add/remove)操作是copy一份副本,在这个副本里操作完成之后再用这个副本替换原件。 当然为了避免出现多个副本,写操作是需要加锁的。这个类适用于读多写特别少的情况,否则很容易OOM。
好多文章都说集合的fail-fast是在并发线程中发生的,其实并不一定是多线程,只是多线程更容易出现。 比如下面这段代码就会抛出ConcurrentModificationException foreach循环使用了iterator
private static void test2() { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); for (String str : list) { list.add("c"); } for (String str : list) { System.out.println(str); } } 复制代码