ConcurrentModificationException快速失敗迭代器異常案例及四種解決方案

異常案例介紹:

基於C/SJFrame界面,使用JPanel模擬管道,每當生成一個管道,便添加到List中,符合某種條件的時候,刪除對應的管道。

算法:遍歷List,一旦元素符合便移除,這樣剩下的便是符合條件的管道;

wKiom1NIo-6ibcYuAADnTRUKfJY660.jpg

運行結果報異常:ConcurrentModificationException

wKioL1NIo8bzblVTAAJGO42Kx6s195.jpg

查詢JDK幫助文檔,才發現這種現象屬於快速失敗迭代器;意思是遍歷集合的時候,不允許對集合做更改。

關於ConcurrentModificationException的詳細解釋,參看以下內容,摘自《JDK API1.6.0

java.util
類 ConcurrentModificationException

java.lang.Object

 java.lang.Throwable

     java.lang.Exception

         java.lang.RuntimeException

java.util.ConcurrentModificationException

所有已實現的接口:

Serializable

public class ConcurrentModificationException

extends RuntimeException

當方法檢測到對象的併發修改,但不允許這種修改時,拋出此異常。

例如,某個線程在 Collection 上進行迭代時,通常不允許另一個線性修改該 Collection。通常在這些情況下,迭代的結果是不確定的。如果檢測到這種行爲,一些迭代器實現(包括 JRE 提供的所有通用 collection 實現)可能選擇拋出此異常。執行該操作的迭代器稱爲快速失敗迭代器,因爲迭代器很快就完全失敗,而不會冒着在將來某個時間任意發生不確定行爲的風險。

注意,此異常不會始終指出對象已經由不同 線程併發修改。如果單線程發出違反對象協定的方法調用序列,則該對象可能拋出此異常。例如,如果線程使用快速失敗迭代器在 collection 上迭代時直接修改該 collection,則迭代器將拋出此異常。

注意,迭代器的快速失敗行爲無法得到保證,因爲一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗操作會盡最大努力拋出 ConcurrentModificationException。因此,爲提高此類操作的正確性而編寫一個依賴於此異常的程序是錯誤的做法,正確做法是:ConcurrentModificationException 應該僅用於檢測 bug。

下面來談如何解決!

解決方案之一:用另一個集合(比如List)記錄要刪除的元素信息,待遍歷完成之後,進行刪除操作;

wKioL1NIo8bwK3GuAAGd5-W8b5k076.jpg

這樣,結果是正確的!

wKioL1NIo8ayUyi_AAEtUpfA7WQ819.jpg

解決方案之二:使用迭代器Iterator,進行元素的刪除操作;

wKiom1NIo--AgLb7AAElB5QhzEc558.jpg

這樣寫,我想應該更合理吧!

wKioL1NIo8bwyWSbAAFHHHHI0vA256.jpg

二者結果完全一樣的!

wKiom1NIo--wQE3WAAE5VASKSGM257.jpg

解決方案之三:使用CopyOnWriteArrayList進行元素的刪除操作,它是ArrayList的一個線程安全的變體,其中所有可變操作(addset 等等)都是通過對底層數組進行一次新的複製來實現的;

前提:CopyOnWriteArrayList替換掉ArrayList

wKiom1NIpWjxD9-_AAB2SDs7sLg059.jpg


wKiom1NIo_TAUZquAAEHEoTBJV4343.jpg

結果正確,方案可行!

wKioL1NIo8yAGAxwAAEgd5tXPzk991.jpg

關於CopyOnWriteArrayList的詳細解釋,參看以下內容,摘自《JDK API1.6.0

java.util.concurrent
類 CopyOnWriteArrayList<E>

java.lang.Object
java.util.concurrent.CopyOnWriteArrayList<E>

類型參數:

E - collection 中所保存元素的類型

所有已實現的接口:

Serializable, Cloneable, Iterable<E>,Collection<E>, List<E>, RandomAccess

public class CopyOnWriteArrayList<E>
extends Object
implements List<E>, RandomAccess, Cloneable, Serializable

ArrayList的一個線程安全的變體,其中所有可變操作(addset等等)都是通過對底層數組進行一次新的複製來實現的。

這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法 有效。在不能或不想進行同步遍歷,但又需要從併發線程中排除衝突時,它也很有用。“快照”風格的迭代器方法在創建迭代器時使用了對數組狀態的引用。此數組在迭代器的生存期內不會更改,因此不可能發生衝突,並且迭代器保證不會拋出ConcurrentModificationException。創建迭代器以後,迭代器就不會反映列表的添加、移除或者更改。在迭代器上進行的元素更改操作(removesetadd)不受支持。這些方法將拋出 UnsupportedOperationException。允許使用所有元素,包括 null

內存一致性效果:當存在其他併發 collection 時,將對象放入 CopyOnWriteArrayList之前的線程中的操作 happen-before隨後通過另一線程從 CopyOnWriteArrayList中訪問或移除該元素的操作。

解決方案之四:如果確定每次遍歷只刪除一個元素,使用break語句跳轉,從而實現元素的刪除操作;

wKiom1NIo_XxKsfGAAEfGapOz34842.jpg

由於這種方法有特殊的條件環境,屬於“自作聰明”的方法,不適應本案例(結果是不正確的),但不失爲一種方法。

wKioL1NIo8yRvzmZAAE845Ls9II730.jpg

總結:以上四種方法,各有各的好處,同時也有不足,這就看你對程序的性能要求了,我的此程序用了多線程,因此首選第二種。PS:其實由於現在的硬件配置,再加上編譯器對代碼的優化,小程序幾乎是比較不出來差別的,所以解決問題、積累經驗是王道。

以上難免有不足之處,還望大家批評指正。

共同學習,共同成長!


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