先來看出錯程式碼:

/*需求:
遍歷已有集合
如果在集合中發現存在字串元素“world”
則在“world”後新增元素“javaee”
*/ List list = new ArrayList();
//多型的形式建立介面實現類物件
list.add("helllo");
list.add("java");
list.add("world"); //生成迭代器並判斷有無world這個元素,如果有則向集合中新增“javaee”
Iterator it = list.iterator();
while(it.hasNext()){
String s = it.next();
if(s.equals("world")){
list.add("javaee");
}
} System.out.println(list);
//丟擲異常:ConcurrentModificationException

這段程式碼中我試圖在迭代的過程中通過list(List實現類物件)呼叫add方法向集合中新增元素並進行輸出,但編譯器在輸出階段丟擲異常並終止了程式執行。

錯誤資訊如下:

下面開始分析問題並找到解決方案:

1. 在錯誤資訊中找到異常名稱,將異常名稱到幫助文件檢視丟擲異常的原因:

2. 通過報錯資訊定位丟擲異常的方法。

錯誤資訊中藍色高亮的部分UseIterator.java:23表示的是丟擲異常的方法所在行數,進一步跟進是ArrayList中的Itr內部類中呼叫的next方法出的問題,再進一步跟進是ArrayList中的next方法呼叫的checkForComodification丟擲的異常。

3.原始碼分析(選中類名ctrl+b檢視原始碼)

丟擲異常的程式碼如下:

List<String> list = new ArrayList<String>();//建立集合物件
Iterator<String> it = list.iterator();//建立迭代器實現類物件 String s = it.next();//丟擲異常的程式碼

從上面的程式碼中可以看到萬惡之源是這個List,所以,先要有一個List,為了看起來更簡潔這裡我只拿了用到的內容過來,其他在這個案例中沒有用到的就暫時沒有管,下面的原始碼也會像這樣簡潔的拿過來看。

public interface List<E> extends Collection<E> {
Iterator<E> iterator();
boolean add(E e);
}

因為list是以多型的形式建立的ArrayList物件,所以也把ArrayList拿過來看看。

public class ArrayList<E> extends AbstractList<E> implements List<E>{
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
/*
expectedModCount:預期修改值
modCount:實際修改值(繼承自父類AbstractList)
*/ @SuppressWarnings("unchecked")
//異常初步定位在內部類Itr的next方法中
public E next() {
checkForComodification();//異常根源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 (modCount != expectedModCount)
//當預期修改值與實際修改值不同丟擲異常
throw new ConcurrentModificationException();
}
}
}

從定義中可以看到ArrayList繼承了AbstractList<E>,Abstract也暫時先把定義拿過來,裡面需要用到的內容後面進行完善。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>();

ArrayList的定義中可以看出,除了繼承AbstractList<E>外,還實現了介面List<E>,所以ArrayList中應該要有add方法的實現,所以將add方法的實現也拿過來看看。

public class ArrayList<E> extends AbstractList<E> implements List<E>{
//重寫介面中的add方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
} private class Itr implements Iterator<E> {
int expectedModCount = modCount;
/*
expectedModCount:預期修改值
modCount:實際修改值(繼承自父類AbstractList)
*/ @SuppressWarnings("unchecked")
//異常初步定位在內部類Itr的next方法中
public E next() {
checkForComodification();
//異常根源checkForComodification()方法
...
} //異常根源
final void checkForComodification() {
if (modCount != expectedModCount)
//當預期修改值與實際修改值不同丟擲該異常
throw new ConcurrentModificationException();
}
}
}

其實到這一步基本上已經能看出來個大概了,Itr實現了List中的Iterator介面,而出錯的程式碼中的it正是Iterator的實現類物件,這裡的Itr就是這個實現類。

Itr在實現next時呼叫了checkForComodification()方法將expectedModCount和modCount進行對比,如果兩邊不等就會丟擲異常。那麼現在要解決的就是expectedModCount和modCount是什麼的問題了。

在Itr中有一個將modCount的值賦值給expectedModCount的操作,其中expectedModCount是itr中的成員變數,但是在ArrayList和Itr中都沒有modCount的宣告,那就只能是一種可能:modCount繼承自父類。所以現在可以完善AbstractList<E>的一部分了

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{
protected transient int modCount = 0;
//實際修改值最初值為0
}

好了,到這裡就破案了,最開始的時候expectedModCount和modCount的值都是0,但是在程式碼中我在迭代過程中通過list呼叫了一下add方法,但是在add方法中每add一次就會對modCount進行一次++,所以導致expectedModCount != modCount,最終丟擲ConcurrentModificationException併發修改異常。

//ArrayList重寫介面中的add方法
public boolean add(E e) {
modCount++;
//每次在集合中新增新的元素後實際修改值++
add(e, elementData, size);
return true;
}

既然現在找到了錯誤的源頭,那就要想一個解決方案出來了,最終目的是查詢是否有“world”這個元素,並向集合中新增元素。迭代器行不通就換一種方法進行迭代,例如for迴圈

for(int i = 0;i<list.size();++i){
String s = list.get(i);//通過get獲取元素
if(s.equals("world")){
list.add("javaee");
}
}

但是這裡又有一個問題,通過get獲取元素為什麼就不會報錯了?空口無憑還是來看看程式碼吧,前面已經分析過的部分就不在贅述了,直接拿過來搞裡頭:

public interface List<E> extends Collection<E> {
boolean add(E e);
E get(int index);
}
public class ArrayList<E> extends AbstractList<E>implements List<E>{ //實現List介面中的get,add方法
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
} public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
}

原始碼說明一切,可以看到ArrayList實現get方法時沒有比較expectedModCount和modCount的操作,所以不管add中對modCount的值++多少次都沒有影響。

下面再捋一下思路。

4. 總結

  1. 編譯器丟擲執行時異常(ConcurrentModificationException)
  2. 追溯異常出現的方法(Itr.next().checkForComodification())
  3. 跟進程式碼找出問題根源。
  4. 找出解決方案:採用for迴圈呼叫list.get()的方式遍歷集合則可以避開checkForComodification()中的if (modCount != expectedModCount)比較,此時再呼叫add向集合新增元素即可。

其中跟進程式碼思路如下:

  • 錯誤程式碼中通過list.iterator獲取到元素迭代器,並通過迴圈呼叫next的方式實現遍歷集合,當發現“world”時會執行add操作
  • ArrayList實現List中的add方法時加入了modCount++的操作,所以當向集合中新增元素後,modCount的值會發生變化
  • next方法呼叫了checkForComodification()方法,該方法中進行了if (modCount != expectedModCount)比較
  • 因為上一迴圈進行了add操作此時modCount值已經發生變化,再次進行比較時modCount != expectedModCount,所以丟擲異常

大概就這些,歡迎指正。