關(guān)于集合中的并發(fā)修改異常及處理方式
前言
關(guān)于集合的總結(jié),可參考如下圖:

在Java中,像ArrayList這樣的集合類使用迭代器的時(shí)候,如果在遍歷過程中直接修改集合(比如remove),可能會(huì)導(dǎo)致ConcurrentModificationException。
為什么是可能會(huì)產(chǎn)生導(dǎo)致ConcurrentModificationException?
因?yàn)樵谛薷募系慕Y(jié)構(gòu)化修改modeCount的過程中,如果修改了集合的索引(索引前移),則不會(huì)發(fā)生異常。
1、并發(fā)異常介紹
ConcurrentModificationException的核心原因是:迭代器檢測(cè)到集合在遍歷過程中被修改,導(dǎo)致狀態(tài)不一致。
1.1、for-each的本質(zhì)
在 Java 中,如果你在for-each循環(huán)(即增強(qiáng)型 for 循環(huán))中直接對(duì)集合執(zhí)行remove()操作,會(huì)拋出ConcurrentModificationException。
這是因?yàn)閒or-each循環(huán)底層依賴于迭代器(Iterator)來遍歷集合,而迭代器在設(shè)計(jì)時(shí)為了保證遍歷的一致性和安全性,對(duì)集合的結(jié)構(gòu)修改有嚴(yán)格的限制。
如下所示,for-each的本質(zhì):
for (Element e : collection) {
// do something
}
等價(jià)于:
Iterator<Element> it = collection.iterator();
while (it.hasNext()) {
Element e = it.next();
// do something
}
1.2、調(diào)用list.remove()的后果
在循環(huán)中直接調(diào)用list.remove()(element)或list.remove()(index),會(huì)直接修改集合的結(jié)構(gòu),導(dǎo)致modCount自動(dòng)遞增。
此時(shí)迭代器的expectedModCount并未更新,因此在下一次調(diào)用it.next()時(shí),會(huì)檢測(cè)到不一致并拋出異常。
2、迭代器
2.1、設(shè)計(jì)原理
Java 集合框架中的迭代器(Iterator)在設(shè)計(jì)時(shí)引入了一種并發(fā)修改檢查機(jī)制,用于在遍歷過程中檢測(cè)集合的結(jié)構(gòu)是否被外部修改。
機(jī)制的核心目的保證迭代過程中集合的一致性,防止因并發(fā)修改導(dǎo)致的不可預(yù)期行為。
1、快速失?。‵ail-Fast)策略
Java 集合的迭代器采用快速失敗策略:一旦檢測(cè)到并發(fā)修改,立即拋出異常,防止后續(xù)操作產(chǎn)生不可預(yù)知的結(jié)果??焖偈〔荒鼙WC 100% 檢測(cè)到所有并發(fā)修改,但能顯著降低錯(cuò)誤概率。
2、安全性與性能的權(quán)衡
并發(fā)檢查機(jī)制增加了少量性能開銷,但保障了迭代過程的安全性。對(duì)于高并發(fā)場(chǎng)景,可選擇線程安全的集合類(如ConcurrentHashMap)。
2.2、并發(fā)檢查機(jī)制的屬性
1.modCount與expectedModCount
modCount:
- 是集合類(如ArrayList、HashMap)中的一個(gè)字段,表示集合的結(jié)構(gòu)性修改次數(shù)(如添加、刪除元素)。
- 每次對(duì)集合進(jìn)行結(jié)構(gòu)性修改(如add
()、remove()),modCount會(huì)自動(dòng)遞增。
expectedModCount:
是迭代器內(nèi)部保存的一個(gè)字段,表示迭代器創(chuàng)建時(shí)集合的modCount值。
- 在調(diào)用it.next()時(shí),迭代器內(nèi)部會(huì)檢查當(dāng)前集合的modCount是否與迭代器創(chuàng)建時(shí)記錄的expectedModCount一致。
- 如果不一致,就會(huì)拋出ConcurrentModificationException。
2.檢查邏輯
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
如果集合的modCount被修改(即modCount != expectedModCount),迭代器會(huì)拋出ConcurrentModificationException,表示檢測(cè)到并發(fā)修改。
2.3、機(jī)制的工作流程
1、迭代器創(chuàng)建時(shí)
迭代器在創(chuàng)建時(shí)會(huì)記錄當(dāng)前集合的modCount值,并將其賦值給expectedModCount。
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int expectedModCount = modCount; // 記錄初始狀態(tài)
...
}
2、迭代過程中調(diào)用next()
每次調(diào)用next()方法時(shí),迭代器會(huì)檢查modCount和expectedModCount是否一致。
public E next() {
checkForComodification(); // 檢查是否被修改
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
3、調(diào)用iterator.remove()
如果通過迭代器的remove()方法刪除集合的元素,此時(shí)迭代器iterator會(huì)同步更新modCount和expectedModCount,確保一致性。
public void remove() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 刪除元素并更新 modCount
ArrayList.this.remove(size - 1);
cursor--;
expectedModCount = modCount; // 同步更新
}
??注意:如果直接使用list.remove(),modCount會(huì)增加,下次再調(diào)用的時(shí)候,會(huì)拋異常。
2.4、適用場(chǎng)景
1.單線程下的結(jié)構(gòu)修改檢測(cè)
該機(jī)制不僅適用于多線程環(huán)境,也適用于單線程中在迭代過程中直接修改集合的情況。
2.多線程下的并發(fā)修改
在多線程環(huán)境下,如果多個(gè)線程同時(shí)修改集合的元素,也可能導(dǎo)致modCount與expectedModCount不一致。
此時(shí),迭代器的并發(fā)檢查機(jī)制可以檢測(cè)到這種沖突,但并不能完全解決線程安全問題。需要結(jié)合線程安全集合(如CopyOnWriteArrayList)或同步機(jī)制。
3、解決方案
3.1、迭代器的remove()
Iterator<Element> it = list.iterator();
while (it.hasNext()) {
Element e = it.next();
if (someCondition(e)) {
it.remove(); // 安全地刪除元素
}
}
it.remove()是迭代器提供的方法,它會(huì)同步更新modCount和expectedModCount,避免異常。
3.2、普通for循環(huán) + 控制索引
for (int i = 0; i < list.size(); i++) {
Element e = list.get(i);
if (someCondition(e)) {
list.remove(i);
i--; // 刪除后索引前移
}
}
- 注意:刪除元素后需要調(diào)整索引,防止跳過元素。
3.3、CopyOnWriteArrayList
如果確實(shí)需要在遍歷過程中頻繁修改集合,可以使用線程安全的CopyOnWriteArrayList:
List<Element> list = new CopyOnWriteArrayList<>();
for (Element e : list) {
if (someCondition(e)) {
list.remove(e); // 不會(huì)拋出異常
}
}
- 該集合在修改時(shí)會(huì)復(fù)制底層數(shù)組,避免直接修改共享數(shù)據(jù),但性能開銷較大。
3.4、Collections.synchronizedlist(list)
在多線程環(huán)境中使用Collections.synchronizedList時(shí),遍歷(Iteration)并修改列表的過程中,必須手動(dòng)加鎖,以確保線程安全。
這是因?yàn)樵谀J(rèn)情況下,Collections.synchronizedList的同步機(jī)制僅覆蓋其方法調(diào)用,但不包括迭代器(Iterator)的線程安全性。
1、為什么需要手動(dòng)加鎖?
- Collections.synchronizedList的同步機(jī)制
- Collections.synchronizedList返回的列表是一個(gè)線程安全的包裝類,其所有方法(如add、remove、get等)都通過synchronized關(guān)鍵字加鎖,確保單個(gè)方法調(diào)用的線程安全。
2.迭代器(Iterator)的線程安全性缺失
- 雖然列表的方法是線程安全的,但迭代器本身并不是線程安全的。
- 例如:如果線程 A 正在遍歷列表(使用iterator
()),而線程 B 同時(shí)修改了列表(如add或remove),即使這些方法是同步的,迭代器也可能拋出ConcurrentModficationException,或者看到不一致的數(shù)據(jù)狀態(tài)。
代碼示例如下:
List<String> list = Collections.synchronizedList(new ArrayList<>());
// 添加元素
list.add("A");
list.add("B");
// 遍歷時(shí)手動(dòng)加鎖
synchronized (list) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("A")) {
iterator.remove(); // 安全地移除元素
}
}
}
小結(jié):

使用迭代器提供的remove()方法、避免在for-each中直接修改集合,或選擇適合的集合類型(如CopyOnWriteArrayList),可以有效避免此類異常。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot實(shí)現(xiàn)簡單的消息對(duì)話的示例代碼
本文主要介紹了springboot實(shí)現(xiàn)簡單的消息對(duì)話的示例代碼,可以使用WebSocket技術(shù),WebSocket是一種在客戶端和服務(wù)器之間提供實(shí)時(shí)雙向通信的協(xié)議,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
詳解使用spring boot admin監(jiān)控spring cloud應(yīng)用程序
本篇文章主要介紹了詳解使用spring boot admin監(jiān)控spring cloud應(yīng)用程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
SpringBoot+easypoi實(shí)現(xiàn)數(shù)據(jù)的Excel導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了SpringBoot+easypoi實(shí)現(xiàn)數(shù)據(jù)的Excel導(dǎo)出,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
Spring?JPA的實(shí)體屬性類型轉(zhuǎn)換器并反序列化工具類詳解
這篇文章主要介紹了Spring?JPA的實(shí)體屬性類型轉(zhuǎn)換器并反序列化工具類詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
IntelliJ IDEA 2018 最新激活碼(截止到2018年1月30日)
這篇文章主要介紹了IntelliJ IDEA 2018 最新激活碼(截止到2018年1月30日)的相關(guān)資料,需要的朋友可以參考下2018-01-01
JDK動(dòng)態(tài)代理與CGLib動(dòng)態(tài)代理的區(qū)別對(duì)比
今天小編就為大家分享一篇關(guān)于JDK動(dòng)態(tài)代理與CGLib動(dòng)態(tài)代理的區(qū)別對(duì)比,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02
java自定義注解驗(yàn)證手機(jī)格式的實(shí)現(xiàn)示例
這篇文章主要介紹了java自定義注解驗(yàn)證手機(jī)格式的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Spring事務(wù)管理中關(guān)于數(shù)據(jù)庫連接池詳解
事務(wù)的作用就是為了保證用戶的每一個(gè)操作都是可靠的,事務(wù)中的每一步操作都必須成功執(zhí)行,只要有發(fā)生異常就 回退到事務(wù)開始未進(jìn)行操作的狀態(tài)。事務(wù)管理是Spring框架中最為常用的功能之一,我們?cè)谑褂肧pring Boot開發(fā)應(yīng)用時(shí),大部分情況下也都需要使用事務(wù)2022-12-12

