参考博文
Java快速失败(fail-fast)和安全失败(fail-safe)区别
Fail-fast机制
在 JDK 的 Collection 中我们时常会看到类似于这样的话:
- ArrayList
注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法,迭代器的快速失败行为应该仅用于检测 bug。
- HashMap
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
源码剖析1( java.util)
ArrayList
@SuppressWarnings("unchecked")
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];
}
// 注意这里
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
HashMap
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
// 注意这里
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null
&& (t = table) != null) {
do {} while (index < t.length
&& (next = t[index++]) == null);
}
return e;
}
源码剖析2(java.util.concurrent)
CopyOnWriteArrayList
解决方案:先复制原有集合内容,在拷贝的集合上进行遍历
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 注意这里
Object[] newElements = Arrays.copyOf(elements,
len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
ConcurrentHashMap
解决方案:增加同步方案
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
...
synchronized (f) {...}
...
}
特别注意
当集合的结构被修改时,modCount才会发生变化。