相信不少同學在處理List的時候遇到過下面的Exception,
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
複製程式碼
話不多說,接下來列舉幾個例子說明問題並且分析其原因。
例一
package main.java.mo.basic;
import java.util.ArrayList;
/**
* Created by MoXingwang on 2017/7/2.
*/
public class ConcurrentModificationExceptionTest {
public static void main(String[] args) {
ArrayList<String> strings = new ArrayList<String>();
strings.add("a");
strings.add("b");
strings.add("c");
strings.add("d");
strings.add("e");
for (String string : strings) {
if ("e".equals(string)) {
strings.remove(string);
}
}
}
}
複製程式碼
- 執行結果
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at main.java.mo.basic.ConcurrentModificationExceptionTest.main(ConcurrentModificationExceptionTest.java:17)
複製程式碼
- 分析原因
首先我們知道增強for迴圈其實現原理就是Iterator介面,這一點非常重要,有了個這個知識點 我們才能分析為什麼會出現異常,這個知識點也是最重要最核心的。
根據上面的異常資訊可以看出,異常是從"for (String string : strings) {",這一行拋 出的,這一行怎麼會出錯呢?理解增強for的實現原理了,我們就會知道,執行這一行程式碼的時候 會呼叫Iterator實現類的兩個方法,hasNext()和next(),所以說這個知識點是最重要最核心 的。
先看ArrayList.Iterator的部分原始碼,以及ArrayList.remove(Object o)的部分原始碼
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
複製程式碼
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
複製程式碼
我們會發現當執行remove(Object o)方法後,ArrayList物件的size減一此時size==4, modCount++了,然後Iterator物件中的cursor==5,hasNext發回了true,導致增強for循 環去尋找下一個元素呼叫next()方法,checkForComodification做校驗的時候,發現modCount 已經和Iterator物件中的expectedModCount不一致,說明ArrayList物件已經被修改過, 為了防止錯誤,丟擲異常ConcurrentModificationException。
回過頭來,再一思考ArrayList的程式碼,讓我們來看看ArrayList本身和內部類Itr,Itr implements Iterator是為了返回給ArrayList.iterator(),在使用的時候可以說他們是 獨立的兩個類,其中各自有兩個重要的屬性;ArrayList中的size、modCount;以及Itr中的 cursor、expectedModCount,理論上他們是同步的,但是我們在某些操作的過程中導致會導致 他們不一致,比如說在這個例子中,我們呼叫的是ArrayList.remove()方法,修改了size和 modCount屬性,但是Itr中的這cursor、expectedModCount卻沒有發生變化,當增強for 迴圈再次執行的時候,呼叫的卻是Itr中的方法,最終發現了資料不一致。這就是本例ConcurrentModificationException 產生的根本原因。
既然問題我們分析清楚了,如何解決呢?這裡我們順著這個思路倒推,列出集中解決辦法。
-
解決問題
- 不使用增強for迴圈
對於這個例子,很明顯我們知道異常的產生原因是由於ArrayList中的屬性和內部類Itr中的 屬性不一致導致的,那麼可以假設在for迴圈和remove操作的時候不設計到Itr類不就得了。 是的,思路很清晰,就這麼簡單。啥都不說先上程式碼。
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c"); strings.add("d"); strings.add("e"); for (int i = 0; i < strings.size(); i++) { String element = strings.get(i); if("e".equals(element)){ strings.remove(element); i --;//需要自己手動維護索引 } } 複製程式碼
使用這種方式處理remove操作,比較尷尬的一點是需要自己手動維護索引,避免漏掉資料。
- 使用Iterator中的remove方法,不要和ArrayList中的remove方法混著搞
基於上面的思路,既然不想和Itr有來望,好吧,看來直接使用Itr類中的remove方法, 使用Itr遍歷物件不也是一個好的想法麼。上程式碼。
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c"); strings.add("d"); strings.add("e"); Iterator<String> iterator = strings.iterator(); while (iterator.hasNext()){ String element = iterator.next(); if("e".equals(element)){ iterator.remove(); } } 複製程式碼
- 刪除元素的時候不再遍歷了,直接removeAll 既然異常是對list做遍歷和remove操作的時候出現的,好吧,暴力點,我能不遍歷的時候做remove操作嗎? 好吧,思路正確,滿足你。
ArrayList<String> strings = new ArrayList<String>(); strings.add("a"); strings.add("b"); strings.add("c"); strings.add("d"); strings.add("e"); ArrayList<String> tempStrings = new ArrayList<String>(); for (String string : strings) { if("e".equals(string)){ tempStrings.add(string); } } strings.removeAll(tempStrings); 複製程式碼
- 其它方法 思路總是多的,比如說加個鎖保證資料正確,什麼去掉這麼到校驗自己實現個ArrayList, 怎麼地都行,你想怎麼玩就怎麼玩,方便點的話直接使用java.util.concurrent包下面的CopyOnWriteArrayList。 方法很多,怎麼開心就好。
例二
說完例一說例二,剛剛是ArrayList,現在試試LinkedList。
package main.java.mo.basic;
import java.util.LinkedList;
/**
* Created by MoXingwang on 2017/7/2.
*/
public class ConcurrentModificationExceptionTest {
public static void main(String[] args) {
LinkedList<String> strings = new LinkedList<String>();
strings.add("a");
strings.add("b");
strings.add("c");
strings.add("d");
strings.add("e");
for (String string : strings) {
if ("e".equals(string)) {
strings.remove(string);
}
}
}
}
複製程式碼
這段程式碼和例一的沒啥區別,唯一不同的就是ArrayList換成了LinkedList,突然發現執行這段程式碼怎麼就不報錯了呢。 這不是搞事情麼?好吧,再上一段程式碼。
package main.java.mo.basic;
import java.util.LinkedList;
/**
* Created by MoXingwang on 2017/7/2.
*/
public class ConcurrentModificationExceptionTest {
public static void main(String[] args) {
LinkedList<String> strings = new LinkedList<String>();
strings.add("a");
strings.add("b");
strings.add("c");
strings.add("d");
strings.add("e");
strings.add("f");
strings.add("g");
for (String string : strings) {
if ("e".equals(string)) {
strings.remove(string);
}
}
}
}
複製程式碼
再執行一下這一段程式碼,返回結果居然是這樣:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:953)
at java.util.LinkedList$ListItr.next(LinkedList.java:886)
at main.java.mo.basic.ConcurrentModificationExceptionTest.main(ConcurrentModificationExceptionTest.java:19)
複製程式碼
仔細一看才發現strings裡面多了兩個元素,怎麼差別就這麼大呢,分析方法和例一完全一樣, 想必按照例子一的分析一定非常簡單的找到答案,這就就不舉例子了。
總結
總得來說,本文雖讓沒有對ConcurrentModificationException發生的情況深入涉及, 但是理解方法和思路都是一樣的,文章中的兩個例子告訴我們, 當在處理Iterable的實現類做元素remove操作,並且是在for迴圈中處理的時候, 理解了這些東西就會避免掉bug以及出現錯誤。