提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在Java集合中有一个非常常见的错误机制——fail-fast机制。这个错误机制主要原因就是多线程对集合的操作会产生错误而发起的。
一、fail-fast机制是什么?
fail-fast机制是java集合中的一种错误机制。当多线程对同一集合的内容进行操作时,就可能会产生fail-fast事件。
在维基百科中解释如下:
在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。
二、fail-fast的解析
首先看一个示例:
看一下处理结果:
现在我们fail-fast实现的方式我们都已经知道了,fail-fast快速失败是在迭代的时候产生的,但是是如何产生的呢?
1.根本原因
从前面我们知道fail-fast是在操作迭代器时产生的。现在我们来看看ArrayList中迭代器的源代码:
从上可知迭代器在调用next()、remove()等方法时都是先调用
checkForComodification()
方法,它检测
if(modCount == expectedModCount)
若不等则抛出ConcurrentModificationException 异常,从而产生fail-fast机制。到了这一步我们也知道了,想要弄清楚fail-fast机制,首先我们需要搞清楚modCount 和expectedModCount。
- expectedModCount 是在IteratorTest中定义的:
int expectedModCount = ArrayList.this.modCount
所以他的值是不可能会修改的,所以会变的就是modCount。
- modCount是在 AbstractList 中定义的,为全局变量:
protected transient int modCount = 0;
那么modCount什么时候因为什么原因而发生改变呢?请看ArrayList的源码:
从上面的源代码我们可以看出,只要是涉及了改变ArrayList元素的个数的方法都会导致modCount的改变。所以我们这里可以初步判断由于expectedModCount 与modCount的改变不同步,导致两者之间不等,从而产生fail-fast机制。
2.如何规避
有以下两种方案:
-
方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList(不推荐)
-
方案二:使用CopyOnWriteArrayList来替换ArrayList。
那么CopyOnWriteArrayList为什么能解决这个问题呢?
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为了避免Copy出N个副本出来,导致并发写。但是CopyOnWriteArrayList中的读方法是没有加锁的。
我们只需要记住一句话,那就是CopyOnWriteArrayList是线程安全的,所以我们在多线程的环境下面需要去使用这个就可以了。
总结
现在我们对fail-fast机制都已经有了了解了。其出现的原因是:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。推荐的解决办法就是用CopyOnWriteArrayList来替换ArrayList