本文原創地址,
我的博客
:https://jsbintask.cn/2019/04/09/jdk/jdk8-concurrentmodificationexception/(食用效果最佳),轉載請註明出處!
前言
ConCurrentModificationException
是jdk用於限制併發情況下容器結構改變的異常類。當一個線程操作一個容器時,此時如果有另一個線程修改了容器大小,將拋出這個異常,我們看下面兩段代碼
Code 2::
說明
:一個線程進行list的排序操作,一個線程移除list中的元素,結果:上面代碼說明不管是單線程情況下還是多線程併發運行模式下,一旦在某些情況下(如上面的遍歷,排序),容器的結構一旦被修改就將拋出
ConCurrentModificationException
。
源碼解析
爲什麼會這樣呢,這樣是不是就代表這些容器是線程安全的呢?
我們通過源碼來討論一下。
首先第一個例子,一個線程進行遍歷操作:for(T t: Collections)
,通過查看字節碼知道,它其實就是使用了Iterator
進行了遍歷操作:
接着查看ArrayList中的Iterator發現它內部是這麼定義的:
ArrayList內部有一個
modCount
成員變量,每次就行修改操作如增加,刪除等會增加該值:而當初始化一個Iterator時,會記錄當前的modCount,以後每次操作(next(), remove())都會檢查該值:
一旦和記錄的初始值不相等,則會拋出異常!
同理,上面的排序操作
sort()
方法同樣是這麼定義的:這樣我們上面的疑問就解開了,之所以會拋出異常,是因爲容器內部維護了一個變量modCount
,在進行某些操作時(iterator,sort)時,會記錄當時的這個值,在操作過程中這個值一旦發生改變則會拋出ConcurrentModificationException。
在jdk
中,這種行爲被稱爲快速失敗
,它的目的是爲了盡最大努力的檢測線程安全!但是! 它並不能保證容器的線程安全
!
這個例子中,我們使用多線程添加了10000個元素,最後成功添加的卻只有9993個元素,說明它內部並沒有保證線程安全! 當我們在併發情況下使用這些容器時就需要考慮線程安全問題,替換線程安全的容器類(如
ConcurrentHashMap, Vector, CopyOnWriteArrayList等
)或者使用額外的同步手段如加鎖!
擴展
在單線程遍歷時,如果想刪除某個元素,可以使用iterator.remove()
:
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add("list->" + (i + 1));
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("list->3")) {
iterator.remove();
}
System.out.println(next);
}
System.out.println(list);
對於ArrayList而言,你也可以調用
lsitIterator()
方法獲取內部的ListIterator
從而進行添加,插入操作:
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add("list->" + (i + 1));
}
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("list->3")) {
iterator.remove();
}
System.out.println(next);
iterator.add("list->7");
}
System.out.println(list);
關注我,這裏只有乾貨!