fail-fast(快速失败)与 fail-safe(安全失败)
fail-fast
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
java.util
中的非线程安全集合的迭代机制都是基于 fail-fast,这里以 ArrayList
为例
原理
在对 ArrayList
的结构进行修改的时候,会修改 modCount
属性,该属性可以理解为 ArrayList
当前结构的版本号
在迭代器创建的时候,会记录 modCount
的值。在迭代的过程中,会判断 modCount
是否被修改,如果 modCount
与原先不一致,则抛出 ConcurrentModificationException
异常
1 | private class Itr implements Iterator<E> { |
如果要在迭代过程中对集合结构进行修改,尽量使用迭代器中提供的方法。大多数迭代器会提供 remove
方法,有的迭代器还会提供 add
操作的方法,如 ListIterator
需要注意的是,在并发的情况下,并不能保证异常抛出
迭代过程中不能对集合的结构进行修改,如果只是修改某个节点的值,并不算修改
测试
1 | private static void failFast() { |
Java官方在HashMap
中对快速失败的描述是对集合结构的修改,但修改某个节点的值并不算修改结构,因此下面这段代码不会抛异常
1 | private static void failFast() { |
fail-safe
在迭代集合的时候,并不是直接遍历集合数据,而是遍历一个副本。当集合被修改时,并不会影响本次迭代。这种迭代方式称为 fail-safe。juc下的安全集合都是fail-safe的实现。以下以CopyOnWriteArrayList
为例
1 | private static void failSafe() { |
COW每次执行 add 操作时,都会修改 array
属性,将新的数组赋值给 array
。因此创建迭代器的时候必须保存副本,确保在迭代的时候不会出现问题。