Java導致ConcurrentModificationException所有原因
1. 什么是 ConcurrentModificationException?
它是 Java 集合框架拋出的運行時異常,表示集合在遍歷過程中被結(jié)構(gòu)性修改了,導致迭代器無法保證一致性。
2. 異常觸發(fā)的底層機制
Java 集合(如 ArrayList、HashSet 等)在創(chuàng)建迭代器時,會記錄集合的結(jié)構(gòu)性修改次數(shù)(modCount)。每次集合結(jié)構(gòu)發(fā)生變化(如 add、remove),modCount 增加。迭代器內(nèi)部有一個 expectedModCount,每次調(diào)用 next()/hasNext() 時,會檢查 modCount 是否和 expectedModCount 一致。如果不一致,就拋出 ConcurrentModificationException。
3. 導致 ConcurrentModificationException 的所有常見原因
3.1 遍歷過程中直接修改集合
錯誤代碼示例:
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if (s.equals("b")) {
list.remove(s); // 錯誤!遍歷時直接修改集合
}
}原因: for-each 底層用的是 iterator,直接用 list.remove() 修改集合,modCount 改變,expectedModCount 沒變,拋異常。
3.2 用 Iterator 遍歷時,直接用集合的 add/remove 方法修改集合
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("b")) {
list.remove(s); // 錯誤!應使用 it.remove()
}
}3.3 多線程并發(fā)修改集合
一個線程遍歷集合,另一個線程同時修改集合(結(jié)構(gòu)性操作),也會拋異常。
示例:
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
new Thread(() -> {
for (Integer i : list) {
System.out.println(i);
}
}).start();
new Thread(() -> {
list.add(4); // 并發(fā)修改
}).start();3.4 在 for-each 循環(huán)中調(diào)用集合的 remove/add
for (String s : list) {
list.remove(s); // 錯誤
}正確做法: 用 Iterator 的 remove 方法。
3.5 對 Map 的 keySet/values/entrySet 進行遍歷時修改 Map
Map<String, Integer> map = new HashMap<>();
map.put("a", 1); map.put("b", 2);
for (String key : map.keySet()) {
map.remove(key); // 錯誤
}3.6 迭代器遍歷時,集合結(jié)構(gòu)被外部方法修改
如果在遍歷過程中調(diào)用了會修改集合結(jié)構(gòu)的方法(即使不是直接在循環(huán)體里),也會導致異常。
3.7 使用 fail-fast 集合(如 ArrayList、HashSet、HashMap)遍歷時結(jié)構(gòu)性修改
Java 的大多數(shù)集合都是 fail-fast 的(快速失敗),即檢測到并發(fā)修改就立即拋異常。
4. 什么是結(jié)構(gòu)性修改?
結(jié)構(gòu)性修改指的是影響集合元素數(shù)量或排列的操作,比如 add、remove、clear、put(Map),而僅僅修改元素內(nèi)容(如 set(index, value))不算結(jié)構(gòu)性修改。
5. 如何避免 ConcurrentModificationException?
用 Iterator 的 remove 方法刪除元素
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("b")) {
it.remove();
}
}用并發(fā)安全集合
如 CopyOnWriteArrayList、ConcurrentHashMap,這些集合不會拋 ConcurrentModificationException。
遍歷前收集要刪除的元素,遍歷后統(tǒng)一刪除
List<String> toRemove = new ArrayList<>();
for (String s : list) {
if (條件) toRemove.add(s);
}
list.removeAll(toRemove);使用 ListIterator 的 add/remove/set 方法
6. 其他補充
ConcurrentModificationException只是 fail-fast 機制的一部分,不能保證百分百檢測到所有并發(fā)修改。- 在多線程環(huán)境下,優(yōu)先使用并發(fā)集合或加鎖處理。
7. 總結(jié)
所有原因本質(zhì):
遍歷過程中集合結(jié)構(gòu)被直接或間接修改,導致迭代器檢測到不一致。
常見場景:
- for-each 循環(huán)中直接 add/remove
- Iterator 遍歷時集合 add/remove
- 多線程并發(fā)修改
- Map 的 keySet/values/entrySet 迭代時修改 Map
避免方式:
- 用 iterator.remove()
- 用并發(fā)集合
- 遍歷前收集、后批量修改
8. 底層原理再深入
8.1 modCount 和 expectedModCount
- 每個 fail-fast 集合(如 ArrayList、HashMap)內(nèi)部有一個
modCount字段,代表結(jié)構(gòu)性修改次數(shù)。 - 創(chuàng)建迭代器時,迭代器保存一份
expectedModCount。 - 每次迭代器操作(如
next()、remove())時,都會檢查modCount是否和expectedModCount一致。 - 如果不一致,說明集合被外部修改,拋出
ConcurrentModificationException。
源碼片段(以 ArrayList 為例):
public E next() {
checkForComodification(); // 檢查
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}9. 特殊集合的處理
9.1 并發(fā)集合不會拋出該異常
CopyOnWriteArrayList、ConcurrentHashMap等并發(fā)集合,內(nèi)部機制不同,不會 fail-fast。- 例如,
CopyOnWriteArrayList每次修改都會復制一份新數(shù)據(jù),迭代器遍歷的是舊快照,不關(guān)心后續(xù)修改。
9.2 老的 Vector、Hashtable
- 這些集合的方法都加了同步鎖(synchronized),不會拋出
ConcurrentModificationException,但性能較低。
10. 實際項目中的規(guī)避策略
10.1 單線程環(huán)境
- 刪除元素用 iterator.remove()。
- 遍歷前收集需要刪除的元素,遍歷后統(tǒng)一刪除。
- 不要在 for-each 或普通 for 循環(huán)中直接 remove/add。
10.2 多線程環(huán)境
- 使用并發(fā)集合(如 CopyOnWriteArrayList、ConcurrentHashMap)。
- 使用同步塊(synchronized)保護遍歷和修改操作。
- 分批處理:先收集需要修改的數(shù)據(jù),后統(tǒng)一操作。
10.3 遍歷 Map 的安全刪除
- 用
Iterator<Map.Entry<K,V>>遍歷,然后用 iterator.remove() 刪除當前 entry。
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getValue() < 10) {
it.remove();
}
}11. 排查和調(diào)試方法
11.1 查看異常堆棧
- 異常堆棧會指向集合的迭代器方法(如 next()),分析調(diào)用鏈,定位哪里修改了集合。
11.2 檢查所有集合修改點
- 搜索代碼中所有對集合的結(jié)構(gòu)性操作(add、remove、clear等),看是否在遍歷期間被調(diào)用。
11.3 多線程場景
- 檢查是否有線程并發(fā)修改集合,必要時加鎖或用并發(fā)集合。
12. 面試延伸問題
- 什么是 fail-fast?什么是 fail-safe?舉例說明。
- fail-fast:檢測到并發(fā)修改立即拋異常(如 ArrayList)。
- fail-safe:迭代器遍歷的是快照,不拋異常(如 CopyOnWriteArrayList)。
- 如何安全地在遍歷過程中刪除集合元素?
- 用 iterator.remove()。
- ConcurrentModificationException 一定能檢測到所有并發(fā)修改嗎?
- 不能,只能檢測到部分典型場景。
- 為什么并發(fā)集合不會拋 ConcurrentModificationException?
- 并發(fā)集合設(shè)計了特殊機制,如快照、分段鎖等,保證遍歷安全。
13. 真實案例分析
案例:批量刪除數(shù)據(jù)庫記錄時同步維護緩存集合
假設(shè)你有一個緩存 List,批量刪除數(shù)據(jù)庫記錄后也要同步刪除 List 中的元素:
// 錯誤做法,可能拋異常
for (User user : cacheList) {
if (shouldDelete(user)) {
cacheList.remove(user);
}
}
// 正確做法
Iterator<User> it = cacheList.iterator();
while (it.hasNext()) {
User user = it.next();
if (shouldDelete(user)) {
it.remove();
}
}14. 代碼示例:多線程并發(fā)修改
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) list.add(i);
Thread t1 = new Thread(() -> {
for (int i : list) {
// 遍歷
}
});
Thread t2 = new Thread(() -> {
list.remove(50); // 并發(fā)修改
});
t1.start(); t2.start();
// 可能拋 ConcurrentModificationException15. 總結(jié)
- ConcurrentModificationException 是集合 fail-fast 機制的體現(xiàn)。
- 本質(zhì)原因是遍歷期間結(jié)構(gòu)性修改集合。
- 規(guī)避方法:用 iterator.remove(),用并發(fā)集合,多線程加鎖,遍歷后統(tǒng)一修改。
- 多線程場景優(yōu)先用并發(fā)集合,普通集合加鎖也可。
- 面試常問底層原理、fail-fast 與 fail-safe、實際場景規(guī)避。
到此這篇關(guān)于Java導致ConcurrentModificationException所有原因的文章就介紹到這了,更多相關(guān)Java導致ConcurrentModificationException原因內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java遍歷集合報錯ConcurrentModificationException的原因分析與解決方法
- 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
相關(guān)文章
springboot利用aspose預覽office文件的實現(xiàn)過程
這篇文章主要給大家介紹了關(guān)于springboot利用aspose預覽office文件的相關(guān)資料,文中通過示例代碼以及圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考價值,需要的朋友可以參考下2021-06-06
MyBatis源碼解析——獲取SqlSessionFactory方式
這篇文章主要介紹了MyBatis源碼解析——獲取SqlSessionFactory方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12

