Fail-Fast Iterators | Fail-Safe Iterators |
---|---|
Fail-Fast iterators doesn’t allow modifications of a collection while iterating over it | Fail-Safe iterators allow modifications of a collection while iterating over it |
These iterators throw ConcurrentModificationException if a collection is modified while iterating over it | These iterators don’t throw any exceptions if a collection is modified while iterating over it |
They use original collection to traverse over the elements of the collection | They use copy of the original collection to traverse over the elements of the collection |
These iterators don’t require extra memory | These iterators require extra memory to clone the collection |
Iterators returned by ArrayList, Vector, HashMap | Iterator returned by ConcurrentHashMap |
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.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.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 需要额外的空间和时间上的开销
- 不能保证遍历的是最新的内容