发现前面博客遗失了一篇ArrayList源码解析的文章,这里就不打算重写了,但是其中关于Fail-Fast机制的知识还是有必要记录一下。
Fail-Fast机制
集合类数据类型都有一个迭代器用于遍历集合,这样的设计是为了能使用共同的方式迭代集合,而不依赖于集合的类型的具体实现,比如无论哪种集合类型,都能使用foreach迭代。
但是在这个迭代过程中,我们需要防止通过原集合的提供的api对集合进行修改。因为这可能导致迭代结果不符合预期,或者是出现角标越界等问题。
- 不符合预期的情况:在迭代过程中,往迭代的位置前插入元素,那么这个元素不会被迭代到。如果修改已经迭代过的元素,那么迭代结果也和当前集合不一致。
- 角标越界的情况:迭代过程中,删除元素,迭代器游标可能超过了集合的长度。
所以在集合的基类中就定义了modCount字段,每当调用会对集合数据结构产生变化的方法时,modCount++。
而迭代器中则定义了expectCount字段,在迭代器创建时,expectCount赋值为modCount,当迭代时出现expectCount不能与modCount时就会抛出ConcurrentModifiedException,这就是所谓的Fail-Fast机制:在迭代过程中可能出现错误的时候,提前抛出并发修改异常,中断迭代操作。
当然,自己实现集合的子类也可以忽略这个字段,不需要Fail-Fast机制。在modCount字段中上面有这么一段文档注释:
If an implementation does not wish to provide fail-fast iterators, this field may be ignored.
容易误解的地方
我看有篇博文说modCount是被volatile修饰的,然后这个小伙伴就将Fail-Fast机制和多线程一起巴拉巴拉的说了一堆……我翻完了jdk各个版本的源码都没发现这个修饰,估计是将transition误以为volatile了。但其实即使是volatile也无法保证modCount线程安全,因为modCount++并不是一个原子性操作……
有的人觉得Fail-Fast机制和多线程有关、modCount是为了多线程设计的,这是很严重的错误的理解。
并不是多线程才会抛出并发修改异常的,而且这个机制也不是针对多线程设计的,modCount对多线程的意义不大,因为主线程中modCount++后,子线程读取到的可能还是自增前的数值,所以多线程情况下反而可能不抛出ConcurrentModifiedException了,导致Fail-Fast机制失效。
Fail-Safe
在线程安全的集合中,迭代器是拷贝集合的副本进行迭代的,所以即使在迭代过程中修改原集合,也不会出现角标越界的情况。但相对的,会出现不符合预期的情况,此时迭代结果和原集合内容已经不一致。
所以这就是Fail-Safe命名的由来,安全的错误。