只要拋出出現異常,能夠確定的是代碼必定有錯誤的地方。先來看看都有哪些狀況會出現ConcurrentModificationException異常,下面以ArrayList remove 操做進行舉例:
使用的數據集合:html
?java
1安全 2多線程 3併發 4dom 5異步 6ide 7測試 |
|
如下三種狀況都會出現異常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
異常信息以下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
以上都有3種出現異常的狀況有一個共同的特色,都是使用Iterator進行遍歷,且都是經過ArrayList.remove(Object) 進行刪除操做。
想要找出根本緣由,直接查看ArrayList源碼看爲何出現異常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
|
List、Set、Map 均可以經過Iterator進行遍歷,這裏僅僅是經過List舉例,在使用其餘集合遍歷時進行增刪操做都須要留意是否會觸發ConcurrentModificationException異常。
上面列舉了會出現問題的幾種狀況,也分析了問題出現的根本緣由,如今來總結一下怎樣纔是正確的,若是避免遍歷時進行增刪操做不會出現ConcurrentModificationException異常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
輸出結果都是:List Value:[1, 2, 4, 5] , 不會出現異常。
以上4種解決辦法在單線程中測試徹底沒有問題,可是若是在多線程中呢?
上面針對ConcurrentModificationException異常在單線程狀況下提出了4種解決方案,原本是能夠很哈皮的洗洗睡了,可是若是涉及到多線程環境可能就不那麼樂觀了。
下面的例子中開啓兩個子線程,一個進行遍歷,另一個有條件刪除元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
輸出結果:
遍歷集合 value = 1
刪除元素 value = 1
遍歷集合 value = 2
刪除元素 value = 2
遍歷集合 value = 3
刪除元素 value = 3
Exception in thread "Thread-0" 刪除元素 value = 4
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
at list.ConcurrentModificationExceptionStudy$1.run(ConcurrentModificationExceptionStudy.java:42)
at java.lang.Thread.run(Unknown Source)
刪除元素 value = 5
結論:
上面的例子在多線程狀況下,僅使用單線程遍歷中進行刪除的第1種解決方案使用it.remove(),可是測試得知4種的解決辦法中的一、二、3依然會出現問題。
接着來再看一下JavaDoc對java.util.ConcurrentModificationException異常的描述:
當方法檢測到對象的併發修改,但不容許這種修改時,拋出此異常。
說明以上辦法在同一個線程執行的時候是沒問題的,可是在異步狀況下依然可能出現異常。
(1) 在全部遍歷增刪地方都加上synchronized或者使用Collections.synchronizedList,雖然能解決問題可是並不推薦,由於增刪形成的同步鎖可能會阻塞遍歷操做。
(2) 推薦使用ConcurrentHashMap或者CopyOnWriteArrayList。
(1) CopyOnWriteArrayList不能使用Iterator.remove()進行刪除。
(2) CopyOnWriteArrayList使用Iterator且使用List.remove(Object);會出現以下異常:
java.lang.UnsupportedOperationException: Unsupported operation remove
at java.util.concurrent.CopyOnWriteArrayList$ListIteratorImpl.remove(CopyOnWriteArrayList.java:804)
單線程狀況下列出4種解決方案,可是發如今多線程狀況下僅有第4種方案才能在多線程狀況下不出現問題。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
輸出結果:
刪除元素 value = 1
遍歷集合 value = 1
刪除元素 value = 2
遍歷集合 value = 2
刪除元素 value = 3
遍歷集合 value = 3
刪除元素 value = 4
遍歷集合 value = 4
刪除元素 value = 5
遍歷集合 value = 5
OK,搞定
《How to Avoid ConcurrentModificationException when using an Iterator》