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 需要额外的空间和时间上的开销
  • 不能保证遍历的是最新的内容

results matching ""

    No results matching ""