Java遍歷集合報錯ConcurrentModificationException的原因分析與解決方法
問題背景:遍歷時修改集合
在項目中,我們經(jīng)常需要對列表進(jìn)行“邊遍歷邊刪除”的操作,比如清理不符合條件的數(shù)據(jù):
List<String> users = new ArrayList<>();
users.add("Tom");
users.add("Jerry");
users.add("Spike");
// 刪除名字長度小于4的用戶
for (String name : users) {
if (name.length() < 4) {
users.remove(name); // 報錯!
}
}
運(yùn)行結(jié)果如下:
Exception in thread "main" java.util.ConcurrentModificationException
很多初學(xué)者會以為是多線程并發(fā)的問題,其實不是——這段代碼只有一個線程。真正的原因在于:我們在使用增強(qiáng) for 循環(huán)(底層是 Iterator)遍歷時修改了集合結(jié)構(gòu)。
異常原理:fail-fast 機(jī)制
Java 的集合類(如 ArrayList、HashMap)在遍歷時會維護(hù)一個“結(jié)構(gòu)修改計數(shù)器(modCount)”。每次調(diào)用 add() 或 remove() 時,modCount 都會遞增。
而在使用 Iterator 遍歷時,迭代器內(nèi)部也保存了一個 expectedModCount,表示它期望的修改次數(shù)。每次 next() 時都會檢查:
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
所以,當(dāng)你在遍歷過程中直接操作原集合(而不是通過迭代器),modCount 會變化,而迭代器的 expectedModCount 沒更新,于是觸發(fā)了“fail-fast”保護(hù)機(jī)制。
解決方案 1:使用 Iterator.remove()
最正統(tǒng)、最安全的方式就是用 Iterator 自帶的 remove() 方法。它會在刪除元素的同時同步更新 expectedModCount,從而避免異常。
List<String> users = new ArrayList<>();
users.add("Tom");
users.add("Jerry");
users.add("Spike");
Iterator<String> iterator = users.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.length() < 4) {
iterator.remove(); // 正確做法
}
}
System.out.println(users);
輸出結(jié)果:
[Jerry, Spike]
沒有報錯,功能正常。
優(yōu)點:
- 不會觸發(fā)
ConcurrentModificationException; - 無需復(fù)制集合;
- 對性能影響小。
缺點:
- 寫法略繁瑣;
- 只能在單線程環(huán)境中安全使用。
解決方案 2:使用 CopyOnWriteArrayList(線程安全版)
如果你的場景是多線程修改集合,比如在 Web 請求或任務(wù)調(diào)度中共享數(shù)據(jù),可以使用 CopyOnWriteArrayList。
這個類在寫操作時會復(fù)制底層數(shù)組,從而保證讀操作不會受寫操作影響。
import java.util.concurrent.CopyOnWriteArrayList;
public class Demo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> users = new CopyOnWriteArrayList<>();
users.add("Tom");
users.add("Jerry");
users.add("Spike");
for (String name : users) {
if (name.length() < 4) {
users.remove(name); // 不會拋異常
}
}
System.out.println(users);
}
}
輸出:
[Jerry, Spike]
優(yōu)點:
- 支持多線程安全修改;
- 不會觸發(fā)并發(fā)修改異常;
- 讀操作幾乎無鎖,非???。
缺點:
- 寫操作開銷較大(每次寫都會復(fù)制整個數(shù)組);
- 不適合頻繁寫入的場景。
解決方案 3:Stream 流式重建集合(推薦現(xiàn)代寫法)
在 Java 8 之后,其實最推薦的做法是使用 Stream 進(jìn)行過濾或重建集合。這種寫法既優(yōu)雅又安全,不會破壞原集合。
List<String> users = new ArrayList<>();
users.add("Tom");
users.add("Jerry");
users.add("Spike");
// 使用 Stream 過濾重建集合
users = users.stream()
.filter(name -> name.length() >= 4)
.toList(); // 重新生成一個新列表
System.out.println(users);
輸出:
[Jerry, Spike]
優(yōu)點:
- 不會拋異常;
- 可讀性好;
- 更符合函數(shù)式編程風(fēng)格;
- 支持并行流提升性能。
缺點:
會生成新的集合對象(有一定內(nèi)存開銷)。
實際開發(fā)場景案例
在一個電商系統(tǒng)中,我們有一個用戶購物車 List<CartItem>,每次請求結(jié)算時,需要過濾掉失效的商品。
開發(fā)者最開始寫了這樣一段代碼:
for (CartItem item : cartItems) {
if (!item.isValid()) {
cartItems.remove(item); // 報 ConcurrentModificationException
}
}
上線后偶發(fā)異常。
后來改成:
Iterator<CartItem> iterator = cartItems.iterator();
while (iterator.hasNext()) {
if (!iterator.next().isValid()) {
iterator.remove(); // 安全刪除
}
}
或者更現(xiàn)代一點:
cartItems = cartItems.stream()
.filter(CartItem::isValid)
.toList(); // 優(yōu)雅且安全
這樣既避免了異常,也讓邏輯更清晰。
小結(jié)
| 方案 | 特點 | 適用場景 |
|---|---|---|
| Iterator.remove() | 原地刪除、安全可靠 | 單線程遍歷 |
| CopyOnWriteArrayList | 線程安全、讀寫分離 | 多線程讀取多、寫入少 |
| Stream 過濾重建 | 優(yōu)雅簡潔、不可變式編程 | 推薦現(xiàn)代開發(fā)風(fēng)格 |
結(jié)語
ConcurrentModificationException 本質(zhì)上是 Java 集合為我們提供的一種保護(hù)機(jī)制,防止在遍歷過程中意外修改數(shù)據(jù)導(dǎo)致不可預(yù)期結(jié)果。只要理解了它的底層原理——“fail-fast 校驗 modCount”,你就能輕松避開這類問題。
簡單記一句話:
遍歷時不要直接改集合,要么用迭代器,要么重建集合。
到此這篇關(guān)于Java遍歷集合報錯ConcurrentModificationException的原因分析與解決方法的文章就介紹到這了,更多相關(guān)Java ConcurrentModificationException異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java ConcurrentModificationException 深度剖析開發(fā)調(diào)試日志的解決方案
- Java?報錯?java.util.ConcurrentModificationException:?null?的原因及解決方案
- Java ConcurrentModificationException異常解決案例詳解
- 詳解Java刪除Map中元素java.util.ConcurrentModificationException”異常解決
- Java源碼解析ArrayList及ConcurrentModificationException
- 出現(xiàn)java.util.ConcurrentModificationException 問題及解決辦法
- java.util.ConcurrentModificationException 解決方法
- java 集合并發(fā)操作出現(xiàn)的異常ConcurrentModificationException
- Java導(dǎo)致ConcurrentModificationException所有原因
相關(guān)文章
SpringBoot整合MybatisPlusGernerator實現(xiàn)逆向工程
在我們寫項目的時候,我們時常會因為需要創(chuàng)建很多的項目結(jié)構(gòu)而頭疼,本文主要介紹了SpringBoot整合MybatisPlusGernerator實現(xiàn)逆向工程,具有一定的參考價值,感興趣的可以了解一下2024-05-05
Spring MVC中基于自定義Editor的表單數(shù)據(jù)處理技巧分享
Spring MVC中基于自定義Editor的表單數(shù)據(jù)處理技巧。需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12
Java?JDK?Validation?注解解析與使用方法驗證
Jakarta?Validation提供了一種聲明式、標(biāo)準(zhǔn)化的方式來驗證Java對象,與框架無關(guān),可以方便地集成到各種Java應(yīng)用中,本文給大家介紹Java?JDK?Validation?注解解析與使用,感興趣的朋友一起看看吧2025-09-09
Java并發(fā)工具類Exchanger的相關(guān)知識總結(jié)
今天給大家?guī)淼奈恼率荍ava工具類Exchanger的相關(guān)知識總結(jié),文中有非常詳細(xì)的介紹及代碼示例,對正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06
Java模擬HTTP Get Post請求實現(xiàn)論壇自動回帖功能
這篇文章主要介紹了Java模擬HTTP Get Post請求實現(xiàn)論壇自動回帖功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09
Java常見問題之javac Hello.java找不到文件的解決方法
剛開始編寫java代碼時,肯定會遇到各種各樣的bug,當(dāng)然對于初學(xué)者這也是能理解的,下面這篇文章主要給大家介紹了關(guān)于Java常見問題之javac Hello.java找不到文件解決的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下。2018-01-01
SpringBoot對接阿里云OSS的詳細(xì)步驟和流程
阿里云對象存儲服務(wù)(Object Storage Service,簡稱OSS)是一種海量、安全、低成本、高可靠的云存儲服務(wù),它適合存儲任意類型的文件,適用于海量數(shù)據(jù)存儲、圖片/視頻存儲、靜態(tài)網(wǎng)站托管等場景,本文給大家介紹了SpringBoot對接阿里云OSS的詳細(xì)步驟和流程2025-08-08
RocketMQ生產(chǎn)者一個應(yīng)用不能發(fā)送多個NameServer消息解決
這篇文章主要為大家介紹了RocketMQ生產(chǎn)者一個應(yīng)用不能發(fā)送多個NameServer消息原因及解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Java中finally和return的關(guān)系實例解析
這篇文章主要介紹了Java中finally和return的關(guān)系實例解析,總結(jié)了二者的關(guān)系,然后分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-02-02

