集合迭代時候操作解決方案-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
當操作完後,替換資料。
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 GC和Full GC。之前我們系統中使用了一個服務由於每晚使用CopyOnWrite機制更新大物件,造成了每晚15秒的Full GC,應用響應時間也隨之變長。
針對記憶體佔用問題,可以通過壓縮容器中的元素的方法來減少大物件的記憶體消耗,比如,如果元素全是10進位制的數字,可以考慮把它壓縮成36進位制或64進位制。或者不使用CopyOnWrite容器,而使用其他的併發容器
3、資料一致性問題。CopyOnWrite容器只能保證資料的最終一致性,不能保證資料的實時一致性。所以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。