【Java筆記】以並發修改異常為例總結的出錯解決辦法


先來看出錯代碼:

/*需求:
遍歷已有集合
如果在集合中發現存在字符串元素“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,所以拋出異常 

         大概就這些,歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM