溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java集合快速失敗與安全失敗的示例分析

發(fā)布時間:2021-11-16 13:29:48 來源:億速云 閱讀:165 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹了Java集合快速失敗與安全失敗的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

    正文

    fail-fast與fail-safe
    • fail-fast快速失敗機制: 是Java集合中的一種機制,在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內(nèi)容進(jìn)行了修改(增加、刪除、修改),則會拋出ConcurrentModificationException。

    • fail-safe安全失敗機制:java.util.concurrent包下的容器都是安全失敗,在遍歷時不是直接在集合內(nèi)容上訪問的,而是先copy原有集合內(nèi)容,在拷貝的集合上進(jìn)行遍歷,因此采用安全失敗的容器可以在多線程下并發(fā)使用,并發(fā)修改。

    fail-fast快速失敗機制
    public class test {
        public static void main(String[] args) {
            testForHashMap();
        }
        private static void testForHashMap() {
            HashMap<String,String> hashMap =new LinkedHashMap<>();
            hashMap.put("1","a");
            hashMap.put("2","b");
            hashMap.put("3","c");
            Iterator<Map.Entry<String,String>> iterator=hashMap.entrySet().iterator();
            while (iterator.hasNext()) {
                hashMap.put("bloom","bloom");
                System.out.println(iterator.next());
            }
        }
    }

    快速失敗機制下修改集合元素觸發(fā)快速失敗,輸出結(jié)果:

    遍歷集合時,新增或者刪除元素,將拋ConcurrentModificationException異常

    Java集合快速失敗與安全失敗的示例分析

    fail-safe安全失敗機制
    public class test {   
        public static void main(String[] args) {
            testForHashTable();
        }
        private static void testForHashTable() {
            Hashtable<String,String> hashtable =new Hashtable();
            hashtable.put("4","d");
            hashtable.put("5","e");
            hashtable.put("6","f");
            Enumeration<String> iterator1=hashtable.elements();
            while (iterator1.hasMoreElements()) {
                hashtable.put("bloom","bloom");
                System.out.println(iterator1.nextElement());
            }
        }
    }

    安全失敗機制下修改集合元素,輸出結(jié)果

    我們可以在遍歷集合的同時,新增、刪除元素

    Java集合快速失敗與安全失敗的示例分析

    小結(jié)一下

    fail-fast,它是Java集合的一種錯誤檢測機制。

    在用迭代器遍歷一個集合對象時,如果遍歷過程中不應(yīng)該對集合對象的內(nèi)容進(jìn)行了修改(增加、刪除、修改),可以新建一個新的集合進(jìn)行操作。

    快速失敗&安全失敗(最全的總結(jié))

    public static void main(String[] args) {
    Hashtable<String, String> table = new Hashtable<String, String>();
    table.put("a", "aa");
    table.put("b", "bb");
    table.put("c", "cc");
    table.remove("c");
    Iterator<Entry<String, String>> iterator = table.entrySet().iterator();
    while (iterator.hasNext()) {
    System.out.println(iterator.next().getValue());
    //采用iterator直接進(jìn)行修改 程序正常
    iterator.remove();
    //直接從hashtable增刪數(shù)據(jù)就會報錯
    table.put("d", "dd");
    //直接從hashtable增刪數(shù)據(jù)就會報錯,hashtable,hashmap等非并發(fā)集合,如果在迭代過程中增減了數(shù)據(jù),就是快速失敗
    table.remove("c");
    }
    System.out.println("-----------");
    Lock lock = new ReentrantLock();
    //即使加上lock,還是會跑出ConcurrentModificationException異常
    lock.lock();
    HashMap<String, String> hashmap = new HashMap<String, String>();
    hashmap.put("a", "aa");
    hashmap.put("b", "bb");
    hashmap.put("c", "cc");
    Iterator<Entry<String, String>> iterators = hashmap.entrySet().iterator();
    while (iterators.hasNext()) {
    System.out.println(iterators.next().getValue());
    // 正常
    iterators.remove();
    //直接從hashtable增刪數(shù)據(jù)就會報錯。
    //hashtable,hashmap等非并發(fā)集合,如果在迭代過程中增減了數(shù)據(jù),會快速失敗 (一檢測到修改,馬上拋異常) 
    //java.util.ConcurrentModificationException
    hashmap.remove("c");
    }
    System.out.println("-----------");
    lock.unlock();
    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
    map.put("a", "aa");
    map.put("b", "bb");
    map.put("c", "cc");
    Iterator<Entry<String, String>> mapiterator = map.entrySet().iterator();
    while (mapiterator.hasNext()) {
    System.out.println(mapiterator.next().getValue());
    map.remove("c");// 正常 并發(fā)集合不存在快速失敗問題
    map.put("c", "cc");// 正常 并發(fā)集合不存在快速失敗問題
    }
    System.out.println("-----------");
    }

    運行該段代碼發(fā)現(xiàn),在Hashtable和HashMap的循環(huán)迭代過程中在容器對象上做“修改”操作的話,是跑出java.util.ConcurrentModificationException異常,在Iterator上做操作不會異常。但是ConcurrentHashMap在容器對象和Iterator對象上都不會拋異常,這是為什么呢?

    (1)首先來介紹兩個概念,快速失敗和安全失敗。

    Iterator的安全失敗是基于對底層集合做拷貝,因此,它不受源集合上修改的影響。java.util包下面的所有的集合類都是快速失敗的,而java.util.concurrent包下面的所有的類都是安全失敗的。

    快速失敗的迭代器會拋出ConcurrentModificationException異常,而安全失敗的迭代器永遠(yuǎn)不會拋出這樣的異常。

    (2)我們查看Hashtable、HashMap、ConcurrentHashMap的在Java API底層的entrySet對象發(fā)現(xiàn),三者都做了對當(dāng)前對象的拷貝,三者的處理方式是一樣的,那區(qū)別在哪里呢?看看獲取下一個entrySet在邏輯上的區(qū)別

    這是Hashtable、HashMap的

    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;
            }

    這是ConcurrentHashMap的

    public final Map.Entry<K,V> next() {
                Node<K,V> p;
                if ((p = next) == null)
                    throw new NoSuchElementException();
                K k = p.key;
                V v = p.val;
                lastReturned = p;
                advance();
                return new MapEntry<K,V>(k, v, map);
            }
    /**
             * Advances if possible, returning next valid node, or null if none.
             */
            final Node<K,V> advance() {
                Node<K,V> e;
                if ((e = next) != null)
                    e = e.next;
                for (;;) {
                    Node<K,V>[] t; int i, n;  // must use locals in checks
                    if (e != null)
                        return next = e;
                    if (baseIndex >= baseLimit || (t = tab) == null ||
                        (n = t.length) <= (i = index) || i < 0)
                        return next = null;
                    if ((e = tabAt(t, i)) != null && e.hash < 0) {
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            e = null;
                            pushState(t, i, n);
                            continue;
                        }
                        else if (e instanceof TreeBin)
                            e = ((TreeBin<K,V>)e).first;
                        else
                            e = null;
                    }
                    if (stack != null)
                        recoverState(n);
                    else if ((index = i + baseSize) >= n)
                        index = ++baseIndex; // visit upper slots if present
                }
            }

    ConcurrentHashMap中的迭代器主要包括entrySet、keySet、values方法。它們大同小異,這里選擇entrySet解釋。當(dāng)我們調(diào)用entrySet返回值的iterator方法時,返回的是EntryIterator,在EntryIterator上調(diào)用next方法時,最終實際調(diào)用到了HashIterator.advance()方法。這個方法在遍歷底層數(shù)組。

    在遍歷過程中,如果已經(jīng)遍歷的數(shù)組上的內(nèi)容變化了,迭代器不會拋出ConcurrentModificationException異常。如果未遍歷的數(shù)組上的內(nèi)容發(fā)生了變化,則有可能反映到迭代過程中。

    這就是ConcurrentHashMap迭代器弱一致的表現(xiàn)。ConcurrentHashMap的弱一致性主要是為了提升效率,是一致性與效率之間的一種權(quán)衡。要成為強一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。

    最后我們看看JDK中對于快速失敗的描述:

    注意,此實現(xiàn)不是同步的。如果多個線程同時訪問一個哈希映射,而其中至少一個線程從結(jié)構(gòu)上修改了該映射,則它必須 保持外部同步。(結(jié)構(gòu)上的修改是指添加或刪除一個或多個映射關(guān)系的任何操作;僅改變與實例已經(jīng)包含的鍵關(guān)聯(lián)的值不是結(jié)構(gòu)上的修改。)這一般通過對自然封裝該映射的對象進(jìn)行同步操作來完成。

    如果不存在這樣的對象,則應(yīng)該使用 Collections.synchronizedMap 方法來“包裝”該映射。最好在創(chuàng)建時完成這一操作,以防止對映射進(jìn)行意外的非同步訪問,如下所示: Map m = Collections.synchronizedMap(new HashMap(...));由所有此類的“collection 視圖方法”所返回的迭代器都是快速失敗 的:在迭代器創(chuàng)建之后,如果從結(jié)構(gòu)上對映射進(jìn)行修改,除非通過迭代器本身的 remove 方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。因此,面對并發(fā)的修改,迭代器很快就會完全失敗,而不冒在將來不確定的時間發(fā)生任意不確定行為的風(fēng)險。

    注意,迭代器的快速失敗行為不能得到保證,一般來說,存在非同步的并發(fā)修改時,不可能作出任何堅決的保證??焖偈〉鞅M最大努力拋出 ConcurrentModificationException。因此,編寫依賴于此異常的程序的做法是錯誤的,正確做法是:迭代器的快速失敗行為應(yīng)該僅用于檢測程序錯誤。

    感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Java集合快速失敗與安全失敗的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI