更多 Java 集合类方面的文章,请参见文集《Java 集合类》
Fail-Fast 机制
A fail-fast system is nothing but immediately report any failure that is likely to lead to failure. When a problem occurs, a fail-fast system fails immediately.
In Java, we can find this behavior with iterators. In case, you have called iterator on a collection object, and another thread tries to modify the collection object, then concurrent modification exception will be thrown. This is called fail-fast.
Fail-fast 机制是 Java 集合中的一种错误机制。 当多个线程对同一个集合的内容进行操作时,就可能会产生 Fail-fast 事件。
符合 Fail-Fast 机制的集合类包括:
-
Map:
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable 虽然 Hashtable 是线程安全的
-
Set:
- HashSet
- LinkedHashSet
- TreeSet
-
List:
- ArrayList
- LinkedList
- Vector 虽然 Vector 是线程安全的
使用 Collections.synchronizedXXX() 创建的线程安全的集合也是 Fail-fast
例如:
Map<String, String> m = new HashMap<String, String>();
m.put("A", "AA");
m.put("B", "BB");
m.put("C", "CC");
Iterator it = m.entrySet().iterator();
while (it.hasNext()) {
m.put("D", "DD");
Map.Entry<String, String> e = (Map.Entry) it.next();
System.out.println(e.getKey() + e.getValue());
}
在通过 Iterator 遍历集合时,修改了集合的结构,因此抛出 ConcurrentModificationException:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMapEntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at MapTest.main(MapTest.java:17)
Fail-fast 机制实现原理
通过 modCount
(修改次数)域来实现。
在构造迭代器 Iterator 时,通过 expectedModCount
记录了当前的修改次数:
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
在通过 put 方法修改 Map 时,将 modCount
(修改次数)域加 1: ++modCount;
。
在依次遍历迭代器 Iterator 时,判断 expectedModCount
是否与 modCount
相等:
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;
}
注意事项
- 迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。
- 如果只是修改了 Map 的值,没有修改 Map 的结构,则不会抛出 ConcurrentModificationException。例如将例子中的代码
m.put("D", "DD");
修改为m.put("A", "AAA");
。
Fail-Safe 机制
使用 java.util.concurrent.ConcurrentHashMap
,无论改变值还是改变结构,都不会抛出 ConcurrentModificationException。
In this case the iterator will make a copy of the internal data structure and iterates over the copied data structure. Thus any modifications done to the internal data structure will not effect the iterator.
原理:在原集合的 copy 上遍历。
存在两个问题:
- 创建 copy 需要额外的空间和时间上的开销
- 不能保证遍历的是最新的内容
引用:
HashMap 的实现原理