Fail-Fast,Fail-Safe,还有ConcurrentModificationException

甜点

关于HashMap里的Fail-Fast机制,我们先来看下这段代码

1
2
3
4
5
6
7
8
9
ArrayList<String> list=new ArrayList<>(3);
list.add("a");
list.add("b");
list.add("c");
Iterator<String> ite=list.iterator();
while (ite.hasNext()){
String temp=ite.next();
list.remove(temp);
}

运行起来,就会抛出java.util.ConcurrentModificationException。原因是在遍历的同时改变了数组的内容。

换成这种形式会更好理解:

1
2
3
4
5
6
7
8
9
10
ArrayList<String> list=new ArrayList<>(3);
list.add("a");
list.add("b");
list.add("c");
int y=list.size();
for(int i=0;i<y;i++){
if("b".equals(list.get(i))){
list.remove(i);
}
}

这里会抛出java.lang.IndexOutOfBoundsException

概念

迭代器(Iterator)在工作的时候,会检查集合(Collection)的内容是否发生了变更,如果发生了变更,则抛出java.util.ConcurrentModificationException
这个机制,叫做Fail-Fast.
常用的集合如:ArrayList、LinkedList、HashMap等,均有此机制的保护。

我们通过HashMap的Iterator的remove方法,来了解这个机制的工作方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
int expectedModCount;  // for fast-fail

public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}

第7行,这个迭代器会检测modCountexpectedModCount是否相等,在别的更多操作里,如next(),也会进行相同的检测。

更多

Fail-Safe的概念和Fail-Fast正相反:
如果一个集合的迭代器(Iterator)在工作的时候,对集合做数据的增删操作(包括多线程),也不会有影响,那么这个集合就是Fail-Safe

例如这段代码:

1
2
3
4
5
6
7
8
9
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> ite=list.iterator();
while (ite.hasNext()){
String temp=ite.next();
list.remove(temp);
}

跑起来就不会有问题。
原因正如它的名字一样:Copy.这个集合在增删的操作时候,会先加上ReentrantLock,然后再将数组的内容copy一份,操作copy的数组,最后再将数组写入回去。例如:CopyOnWriteArrayList、CopyOnWriteArraySet

另外一类的Fail-Safe集合的机制与此略有区别,逻辑也更为复杂,
目前有这些集合类是Fail-Safe的:ConcurrentHashMap、ConcurrentLinkedQueue等Concurrent开头的集合。

总结

避免ConcurrentModificationException的方法是:
对集合进行迭代操作的时候,不要变更集合的内容,也不能在别的线程操作集合。
如果确实需要面对这种业务场景,可以选择是Fail-Safe的集合。