Fail-Fast 原理分析
什么是 Fail-Fast
fail-fast 是一种设计思想,一旦检测到可能发生错误,就立马抛出异常,程序不继续往下执行
下面是一个很简单的例子,在循环的途中删除一个节点,程序就会抛出异常
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
for (Integer i: list) {
list.remove(i);
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at collection.fastfail.ListTest2.main(ListTest2.java:19)
Process finished with exit code 1
报错原因
报错的例子中,里面循环实际上是用到了迭代器 iterator ,这点可以使用 javap 得知
而从报错信息里也可以看出是 next() 方法报错,ArrayList 中 iterator 实现类叫 Itr,进入 Itr 类的 next 方法,每次迭代取下一个元素的时候,可以看到有 checkForComodification
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
从 checkForComodification 可以看到如果 modCount != expectedModCount,就会抛出上述异常。里面的 modeCount 属性是记录了当前这个 list 对象的修改的次数,add 和 remove 方法都会对其做加 1操作,expectedModCount 属性则是 iterator 方法被调用后的修改的次数,所以在 iterator 方法之后调用 add 方法会使 modeCount 和 expectedModCount 不等,最终抛出 ConcurrentModificationException
这么设计的原因
至于使用 fail-fast 的原因个人是这么认为的:就 ArrayList 而言,假设在迭代的过程中,别的线程添加或者删除了一个元素,那么此时的选择会有两个
1)应变,这样的话就需要保证添加或者删除操作的线程安全性,否则就没有意义。但这样做又会使得 ArrayList 变成线程安全,损失了性能,和开始的设计初衷不符
2)不应变,如果不去影响别的线程添加或者删除元素,就需要在创建迭代器的时候就创建副本,导致内存增加
从结果上看,设计者偏向于不应变,同时为了不增加内存,选择抛出异常的方式拒绝别的线程去添加或者删除元素,也就是现在的 fail-fast。其实 fail-fast 只是 Java 中的一种并发修改策略,接下来将依次介绍所有的并发修改策略
并发修改策略
策略总共有四种,分别是 fail-fast,weakly consistent ,snapshot ,undefined 。并不存在网上流传的 fali-safe
1)fail-fast
多数不支持并发的集合,都采用 fail-fast 策略的 iterator,比如在 ArrayList 类中有这样的描述
* <p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
简单翻译一下就是 iterators 或者 listIterator 采用 fail-fast 的并发策略:当 iterator 创建好之后,不通过 iterator 的 remove 或者 add 方法去修改 list 的话,就会抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是在未来忍受任意的、不确定的行为
2)weakly consistent
弱一致性。多数 java.util.concurrent 包中的集合,比如 ConcurrentHashMap 采用的是 weakly consistent 策略,在 java.util.concurrent 包的描述中有这么一段
* <p id="Weakly">Most concurrent Collection implementations
* (including most Queues) also differ from the usual {@code java.util}
* conventions in that their {@linkplain java.util.Iterator Iterators}
* and {@linkplain java.util.Spliterator Spliterators} provide
* <em>weakly consistent</em> rather than fast-fail traversal:
* <ul>
* <li>they may proceed concurrently with other operations
* <li>they will never throw {@link java.util.ConcurrentModificationException
* ConcurrentModificationException}
* <li>they are guaranteed to traverse elements as they existed upon
* construction exactly once, and may (but are not guaranteed to)
* reflect any modifications subsequent to construction.
* </ul>
翻译过来就是多数的并发集合(包括多数的队列)的 Iterators 和 Spliterators 采用 weakly consistent ,特征如下:
1)支持并发处理
2)永远不会抛出 ConcurrentModificationException
3)可以反映任何构造之后的修改
3)snapshot
快照。比较典型的是 CopyOnWriteArrayList,类中有这样的描述
* The "snapshot" style iterator method uses a
* reference to the state of the array at the point that the iterator
* was created. This array never changes during the lifetime of the
* iterator, so interference is impossible and the iterator is
* guaranteed not to throw {@code ConcurrentModificationException}.
* The iterator will not reflect additions, removals, or changes to
* the list since the iterator was created. Element-changing
* operations on iterators themselves ({@code remove}, {@code set}, and
* {@code add}) are not supported. These methods throw
* {@code UnsupportedOperationException}.
翻译过来就是 snapshot 策略的 iterator 在创建时会引用一个状态点,或者说快照,副本。副本不会被外部线程影响,也不会抛出 ConcurrentModificationException,而且 iterators 的 remove,set,add 方法也将不被支持,抛出 UnsupportedOperationException
4)undefined
未定义。策略的含义就是什么都不做,既不抛出异常,也不创建副本,可能导致不一致的结果。例子是 Vector 和 Hashtable ,以及它们返回 Enumeration 的方法
如何解决
针对 ArrayList 的问题,下面提供了几种解决方法
1)使用 for i 来获取元素,因为底层是 goto,可以不受 iterator 的影响,但会出现结果不一致问题
2)使用并发包中的类,比如 CopyOnWriteArrayList
3)使用 iterator 提供的删除方法
Iterator<Intger> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals(1)) {
iterator.remove();
}
}
Tips:ArrayList 的 iterator 并非完全不支持修改,影响的只有 add 和 remove,在迭代的时候调用 set 是不会抛出 ConcurrentModificationException 的
参考
https://stuartmarks.wordpress.com/2016/07/27/there-is-no-such-thing-as-a-fail-safe-iterator-in-java/

浙公网安备 33010602011771号