先看下边一段代码。
@Test
public void test(){
List<String> list = new ArrayList();
list.add("1");
list.add("1");
list.add("1");
list.add("1");
list.add("1");
for (String s : list){
list.add("2");
System.out.println(s);
}
}
运行上边这一段代码会报一个java.util.ConcurrentModificationException的异常,。
为什么会报这样的异常,因为java的fail-fast机制
Fail-fast 机制是 Java 集合中的一种错误机制。 当一个线程对集合进行迭代,其他线程对该集合进行内部结构改变。或者该线程在迭代的时候又对其进行改变,就可能会产生 Fail-fast 事件。
- 那java的Fail-fast机制是什么实现的。
在HashMap,ArrayList,LinkedList源码中都有modCount这个全局变量。在add/put,remove,clear等对集合内部结构发生变化的时候,都会改变modCount这个变量,modCount 初始为0,可以说modCount记录了该集合的改变次数。(前边三篇关于集合的文章并没有对modCount进行解释,在这里解释一下)
下边通过源码看一下如何抛出java.util.ConcurrentModificationException这个异常的。
//增强for循环遍历和迭代器遍历都需要用到 Iterator 中的next方法。
public E next() {
//也就是在这一步触发的。
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
//检查modCount 是否和expectedModCount相等。
//modCount我们知道每发生一次变化就会对其进行改变
//而expectedModCount在创建时 就会被赋值
//所以在遍历的时候,只要modCount发生改变,则就会抛出异常。
int expectedModCount = modCount;
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
注意事项
这里异常抛出条件是检测modCount != expectedModCount,如果集合内发生变化,然后恰好modCount = expectedModCount ,那么则不会抛出异常。因此不能依赖是否抛出异常来进行并发编程。这个异常仅供检测使用。
在java.util包下的集合基本上都是快速失败的。
在java.concurrent包下是安全失败的。
fail-safe 安全失败
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。