Java 集合 Fail-Fast 机制 VS Fail-Safe 机制

更多 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 m = new HashMap();
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 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[] 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 nextNode() {
    Node[] t;
    Node 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 的实现原理

你可能感兴趣的:(Java 集合 Fail-Fast 机制 VS Fail-Safe 机制)