相信很多同窗在處理List的時候遇到過下面的Exception,java
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)
複製代碼
話很少說,接下來列舉幾個例子說明問題而且分析其緣由。bash
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接口,這一點很是重要,有了個這個知識點 咱們才能分析爲何會出現異常,這個知識點也是最重要最核心的。ui
根據上面的異常信息能夠看出,異常是從"for (String string : strings) {",這一行拋 出的,這一行怎麼會出錯呢?理解加強for的實現原理了,咱們就會知道,執行這一行代碼的時候 會調用Iterator實現類的兩個方法,hasNext()和next(),因此說這個知識點是最重要最核心 的。this
先看ArrayList.Iterator的部分源碼,以及ArrayList.remove(Object o)的部分源碼spa
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 產生的根本緣由。code
既然問題咱們分析清楚了,如何解決呢?這裏咱們順着這個思路倒推,列出集中解決辦法。對象
解決問題索引
對於這個例子,很明顯咱們知道異常的產生緣由是因爲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操做,比較尷尬的一點是須要本身手動維護索引,避免漏掉數據。
基於上面的思路,既然不想和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();
}
}
複製代碼
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,如今試試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以及出現錯誤。