fail-fast
首先啥是fail-fast?下面引用别人的解释:
在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。
他的意思就是写程序先考虑异常情况,如果遇到就直接抛出处理。
1 | public int divide(int divisor,int dividend){ |
例如上面的方法,对除数进行检测如果是0则直接抛出异常,并标明异常原因。这样做的好处就是可以预先识别出一些错误情况,一方面可以避免执行复杂的其他代码,另外一方面,这种异常情况被识别之后也可以针对性的做一些单独处理。
集合中的fail-fast
我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException。下面来看看例子:
1 | List<Integer> list = new ArrayList<Integer>(){{ |
上面使用foreach来遍历list并删除元素值为5的元素,执行结果:
1 | Exception in thread "main" java.util.ConcurrentModificationException |
可以看到抛出了ConcurrentModificationException异常,这是因为list的fast-fail的机制。
list源码分析
下面是ArrayList里的内部类Itr的remove()源码,可以看到在执行移除前会执行checkForComodification()方法。
1 | public void remove() { |
1 | final void checkForComodification() { |
可以看到这里就是抛出ConcurrentModificationException异常的地方,那么modCount和expectedModCount分别代表什么呢?
- modCount:ArrayList的成员变量,代表集合的修改次数,初始值为0
- expected:Itr的成员变量,代表迭代器预期集合的修改次数,初始值等于创建itr时的修改次数,只有通过迭代器对集合进行操作,该值才会改变。
通过foreach来遍历集合,其实就是通过集合的迭代器(Iterator)来遍历的,如果不通过迭代器来修改集合内的元素,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。
fail-safe
fail-safe字面理解安全失败,java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
集合中的fail-safe
1 | List<Integer> list = new CopyOnWriteArrayList<Integer>(){{ |
上面使用CopyOnWriteArrayList这个类来演示fail-safe,这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。