一、什么是快速失败,什么是安全失败?
1.1 快速失败,是java集合(Collection)中的一种错误检测机制。
1.2 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
二、出现场景(单线程、多线程都可能出现)
2.1 单线程
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
//为集合添加100个数
for (int i = 0; i < 100; i++) {
list.add(i + "");
}
//获取集合的迭代器
Iterator<String> iter = list.iterator();
//我们在集合中删除一个数据,注意我们是直接用集合操作而不是迭代器
list.remove("3");
//再使用迭代器获取数据
iter.next();
}

再来看看ArrayList的源码
public class ArrayList<E> ,,,,,
{
....
//modCount:集合的修改次数
protected transient int modCount = 0;
//当出现增删操作时,便会modCount++;
public E remove(int index) {
....
modCount++;
......
}
....
//通过此方法获取迭代器
public Iterator<E> iterator() {
return new Itr();
}
....
//迭代器是ArrayList的一个内部类
private class Itr implements Iterator<E> {
...
//每次获取一个迭代器,就新建一个对象,将expectedModCount赋值为modCount
int expectedModCount = modCount;
.....
@SuppressWarnings("unchecked")
public E next() {
//检查expectedModCount和modCount是否相等
checkForComodification();
.....
}
//这个就是检查的方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
.....
}
那么上面报错也就不难解释了:
假设创建一个迭代器时modCount=10,那么expectedModCount=10(注意,modCount是ArrayList的参数,而expectedModCount是迭代器的参数,从创建迭代器之后,他们就没联系了)
当执行list.remove("3");操作时,modCount++=11;而此时expectedModCount=10;当我们再执行iter.next()时,很明显两个数不一致,抛出异常。
2.2 多线程
public class test {
private static List<String> list = new ArrayList<String>();
public static void main(String[] args) {
for (int i = 0; i < 100; i++)
list.add(i + "");
System.out.println("线程开始了");
new threadOne().start();
new threadTwo().start();
}
//线程1每隔10ms遍历一次
private static class threadOne extends Thread {
public void run() {
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
System.out.println("线程1遍历到了:" + iter.next());
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//线程2每隔10ms删除一个
private static class threadTwo extends Thread {
public void run() {
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
System.out.println("线程2删除了:" + iter.next());
iter.remove();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

看看迭代器的remove()操作
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
线程1删除时执行到语句1,modCount+1。
由于语句2还没执行,expectedModCount不会改变
这时处理器跳转到线程2执行。
线程2执行next()时,对比modCount和expectedModCount。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
发现两值不一致,抛出ConcurrentModificationException
2.3 结论
无论是单线程/多线程下的add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值。类似的,hashMap中发生的原理也是一样的。
三、如何避免fail-fast
3.1 调用迭代器的remove方法
在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法。
但是该方法remove不能指定元素,只能remove当前遍历过的那个元素,这也是该方法的局限性。
3.2 使用“java.util.concurrent包下的类”去取代“java.util包下的类
private static List<String> list = new CopyOnWriteArrayList<String>();