1. 程式人生 > >集合迭代時候操作解決方案-ArrayList-Iterator-CopyOnWriteArrayList

集合迭代時候操作解決方案-ArrayList-Iterator-CopyOnWriteArrayList

1、集合在使用iterator迭代器,迭代集合的時候,不可以對集合類進行操作,否則會通過jdk java程式碼fail-fast(快速報錯機制)丟擲異常。

fail-fast(快速報錯機制)解釋(fail-fast機制它是Java集合的一種錯誤檢測機制):

集合的內部都維護了一個modCount(版本號),在每一次put、remove的時候,都會對版本號加1。當獲取結合的iterator,會維護一個期望值(expectedModCount),

iterator每次呼叫next()方法,都會判斷 期望值(expectedModCount)是否與集合的modCount(版本號)相等,來判斷是否對集合進行了操作,操作了集合就丟擲異常

2、集合在迭代的時候,還想要刪除集合內的元素的方法

1、使用iterator自帶的remove()方法,對expectedModCount = modCount變數進行了維護,所以不會丟擲異常

        2、使用jdk提供的同步集合,在遍歷使用集合的迭代器(同步集合對Iterator迭代器進行了重新實現,取消了expectedModCount)遍歷集合的時候,也可以對集合進行操作

HashMap->ConcurrentHashMap、ArrayList->CopyOnWriteArrayList

3通過CopyOnWriteArrayList原始碼理解其併發原理,Copy-On-Write

簡稱COW(理解一種設計方式)。當對原集合增刪改操作的時候,維護一份集合的快照,增刪改操作操作的時候都是在新的快照上並新增鎖機制,迭代的操作在舊的快照上。

當操作完後,替換資料

CopyOnWriteArrayList add(E) remove(int index)都是對新的陣列進行修改和新增。所以在多執行緒操作時不會出現java.util.ConcurrentModificationException錯誤。
程式碼示例:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();                         //加鎖
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);  //拷貝快照
            newElements[len] = e;
            setArray(newElements);           //替換資料
            return true;
        } finally {
            lock.unlock();
        }
    }


總結

1、使用場景:CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裡,比如快取。發生修改時候做copy,新老版本分離,保證讀的高效能

2記憶體佔用問題。因為CopyOnWrite的寫時複製機制,所以在進行寫操作的時候,記憶體裡會同時駐紮兩個物件的記憶體,舊的物件和新寫入的物件(注意:在複製的時候只是複製容器裡的引用,只是在寫的時候會建立新物件新增到新容器裡,而舊容器的物件還在使用,所以有兩份物件記憶體)。如果這些物件佔用的記憶體比較大,比如說200M左右,那麼再寫入100M資料進去,記憶體就會佔用300M,那麼這個時候很有可能造成頻繁的Yong GCFull GC。之前我們系統中使用了一個服務由於每晚使用CopyOnWrite機制更新大物件,造成了每晚15秒的Full GC,應用響應時間也隨之變長。

針對記憶體佔用問題,可以通過壓縮容器中的元素的方法來減少大物件的記憶體消耗,比如,如果元素全是10進位制的數字,可以考慮把它壓縮成36進位制或64進位制。或者不使用CopyOnWrite容器,而使用其他的併發容器

3資料一致性問題。CopyOnWrite容器只能保證資料的最終一致性,不能保證資料的實時一致性。所以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。