Java 快速失敗和安全失敗

我們都接觸 HashMap、ArrayList 這些集合類,這些在 java.util 包的集合類就都是快速失敗的;而 java.util.concurrent 包下的類都是安全失敗,比如:ConcurrentHashMap。

1. 快速失敗(fail-fast)

在使用迭代器對集合對象進行遍歷的時候,如果 A 線程正在對集合進行遍歷,此時 B 線程對集合進行修改(增加、刪除、修改),或者 A 線程在遍歷過程中對集合進行修改,都會導致 A 線程拋出 ConcurrentModificationException 異常。數組長度不變的情況只是替換某個位置的元素,不會引起ConcurrentModificationException 異常。

具體效果我們看下代碼:

        HashMap hashMap = new HashMap();
        hashMap.put("不只Java-1", 1);
        hashMap.put("不只Java-2", 2);
        hashMap.put("不只Java-3", 3);

        Set set = hashMap.entrySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
            hashMap.put("下次循環會拋異常", 4);
            System.out.println("此時 hashMap 長度爲" + hashMap.size());
        }

執行後的效果如下圖:

爲什麼在用迭代器遍歷時,修改集合就會拋異常時?

原因是迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變 modCount 的值。

每當迭代器使用 hashNext()/next() 遍歷下一個元素之前,都會檢測 modCount 變量是否爲 expectedModCount 值,是的話就返回遍歷;否則拋出異常,終止遍歷。

2. 安全失敗(fail-safe)

明白了什麼是快速失敗之後,安全失敗也是非常好理解的。

採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷。

由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,故不會拋 ConcurrentModificationException 異常

我們上代碼看下是不是這樣

    ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
    concurrentHashMap.put("不只Java-1", 1);
    concurrentHashMap.put("不只Java-2", 2);
    concurrentHashMap.put("不只Java-3", 3);

    Set set = concurrentHashMap.entrySet();
    Iterator iterator = set.iterator();

    while (iterator.hasNext()) {
        System.out.println(iterator.next());
        concurrentHashMap.put("下次循環正常執行", 4);
    }
    System.out.println("程序結束");

運行效果如下,的確不會拋異常,程序正常執行。

最後說明一下,快速失敗和安全失敗是對迭代器而言的。併發環境下建議使用 java.util.concurrent 包下的容器類,除非沒有修改操作。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章