fast-fail和fast-safe机制
前言
在之前的JDK8 ArrayList源码解析P1中提到过,在对ArrayList进行遍历过程中,如果对集合进行结构性修改(增,删),会导致快速失败(fast-fial) 。那什么是fast-fail
,什么又是safe-fail
呢?
fast-fail(快速失败)
在使用for循环遍历一个集合时,如果此时改变了集合的结构,如增、删,则会抛出ConcurrentModificationException
异常,这个就称之为fast-fail。
原因:
- 集合类中有一个
modCount
变量,在向集合中增加或者删除元素时都会修改这个变量的值; - 每当迭代器使用
hasNext()
/next()
遍历下一个元素之前都会检测modCount
变量和expectedModCount
值是否相等; - 如果相等的话就返回遍历,否则抛出异常,终止遍历。
具体代码如下:
final void checkForComodification() {
if (modCount != expectedModCount) // 1
throw new ConcurrentModificationException();
}
需要注意的是,在序号1处,异常的抛出条件时检测到modCount != expectedmodCount
这个条件。如果集合发生变化时修改modCount
值, 刚好有设置为了expectedmodCount
值, 则异常不会抛出。比如删除了数据,再添加一条数据。所以不能依赖于这个异常是否抛出而进行并发操作的编程, 这个异常只建议检测并发修改的bug。
java.util包下的集合类都是快速失败机制的, 不能在多线程下发生并发修改(迭代过程中被修改)。
fail-safe ( 安全失败 )
采用safe-fail
安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先copy原有集合内容,在拷贝的集合上进行遍历。
原理:
由于迭代时是对原集合的拷贝的值进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会出发ConcurrentModificationException。
缺点:
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地, 迭代器并不能访问到修改后的内容。 (简单来说就是, 迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的)。
java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。
总结
- java.util包下的集合类都是快速失败机制的, 不能在多线程下发生并发修改(迭代过程中被修改);
- java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改;