fail-fast與fail-safe在Java集合中的應用

fail-fast與fail-safe簡介

如果一個系統,當有異常或者錯誤發生時就立即中斷執行,這種設計稱之爲fail-fast。相反如果我們的系統可以在某種異常或者錯誤發生時繼續執行,不會被中斷,這種設計稱之爲fail-safe

fail-fast與fail-safe在Java迭代器中的設計

在Java中,最典型的fail-fast與fail-safe就是關於迭代器的設計。通常情況下,那些線程不安全的集合類產生的迭代器都是fail-fast的,而線程安全的集合類產生的迭代器是fail-safe的。fail-fast的迭代器會在迭代過程中,如果你修改了集合類裏的內容,則會拋出ConcurrentModificationException異常。fail-safe的迭代器則可以在迭代過程中任意修改集合類的內容,不會有異常拋出。

Java的fail-fast迭代器

前面說過,線程安全的集合類產生的迭代器是基於fail-fast設計的,例如ArrayList。這種迭代器迭代過程中是直接訪問原數據信息的,所以當原集合內容修改了後,迭代器不能保證正確的迭代過程。代碼示例如下:

    public static void failFast() {

        List<String> list = new ArrayList<>();

        list.add("item-1");
        list.add("item-2");
        list.add("item-3");
        list.add("item-4");

        Iterator<String> it = list.iterator();

        while (it.hasNext()) {
            String item = it.next();
            System.out.println(item);
            list.add("itme-5"); // 下次迭代時會拋出ConcurrentModificationException異常
        }

    }

通過分析ArrayList源碼可以看出,當對ArrayList做添加或者刪除元素的操作時,都會修改modCount這個變量,而ArrayList的迭代器每次迭代的時候,又都回去檢查當前modCount和迭代器產生時的expectedModCount變量是否相等,如果不等就會拋出ConcurrentModificationException異常。

protected transient int modCount = 0;

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 上面那個方法調用後會修改modCount
    ....
}


// ArrayList的迭代器

private class Itr implements Iterator<E> {

    public E next() {
        checkForComodification();
        ...
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
    
    ...
}

Java的fail-safe迭代器

對於那些線程安全的集合類,在調用iterator方法產生迭代器的時候,會將當前集合的素有元素都做一個快照,即複製一份副本。每次迭代的時候都是訪問這個快照內的元素,而不是原集合的元素。代碼示例如下:

    public static void failSafe() {

        List<String> list = new CopyOnWriteArrayList<>();

        list.add("item-1");
        list.add("item-2");
        list.add("item-3");
        list.add("item-4");

        Iterator<String> it = list.iterator();

        while (it.hasNext()) {
            String item = it.next();
            System.out.println(item);
            list.add("itme-5");
        }

        System.out.println(list.size()); // 會打印出來8,迭代四次,四個新元素插入到了集合中。
    }

這種設計的好處是保證了在多線程操縱同一個集合的時候,不會因爲某個線程修改了集合,而影響其他正在迭代訪問集合的線程。缺點是,迭代器不能正確及時的反應集合中的內容,而且一定程度上也增加了內存的消耗。

迭代器小提示

如果用Java的for loop來訪問集合,原理上還是用迭代器的方式,所以下面的代碼同樣會拋出ConcurrentModificationException異常。

        List<String> list = new ArrayList<>();

        list.add("item-1");
        list.add("item-2");
        list.add("item-3");
        list.add("item-4");
        
        for (String item : list) {
            System.err.println(item);
            list.add("itme-5");
        }

fail-safe與fail-fast的區別

當我們對集合結構上做出改變的時候,fail-fast機制就會拋出異常。但是,對於採用fail-safe機制來說,就不會拋出異常(大家估計看到safe兩個字就知道了)。

這是因爲,當集合的結構被改變的時候,fail-safe機制會在複製原集合的一份數據出來,然後在複製的那份數據遍歷

因此,雖然fail-safe不會拋出異常,但存在以下缺點

  1. 複製時需要額外的空間和時間上的開銷。
  2. 不能保證遍歷的是最新內容。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章