ConcurrentModificationExceptionから、反復器のソースコードを分析

4363 ワード

反復器ソース解析
次の(Java)コードを実行します.
ArrayList list = new ArrayList<>();
list.add(2);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    Integer integer = iterator.next();
    list.remove(integer);
}

以上のコード実行時にjava.util.ConcurrentModificationException実行時異常が投げ出されます.
ArrayListのソースコードに入って異常放出時の条件を確認する:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

/**
 * modCount     list        ,     list size     
 */
protected transient int modCount = 0;


同時にexpectedModCountは、ArrayListによって実装されるIterator反復器インタフェースItrの属性であり、反復器実装クラスItrについて以下で説明する.
private class Itr implements Iterator {
    int cursor;       // index of next element to return,        
    int lastRet = -1; // index of last element returned; -1 if no such,              
    
    //   list    ,       modCount  ,               list.remove(), add()    ,
    //   modCount  , itr.expectedModCount       ,          java.util.ConcurrentModificationException  
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        //          ,  modCount expectedModCount  ,      list     
        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;//      1
        return (E) elementData[lastRet = i];// lastRet  
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            //            ,       next()    lastRet  
            ArrayList.this.remove(lastRet);
            //         ,       next()        1,               
            //ArrayList.this.remove(lastRet)   list lastRet+1   size-1              ,
            //                     ,           lastRet   
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }


    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
ArrayList.this.remove(lastRet)の具体的な実装を分析します.
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0){
        //remove     ,       
        //     index+1 size-1         ,  size       
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    }
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

まとめ
リストを巡る過程で元のリストを削除する
単一スレッドの場合
ArrayList list = new ArrayList<>();
list.add(2);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    Integer integer = iterator.next();
    //      remove           list     modCount = expectedModCount
    iterator.remove();
}

マルチスレッドの場合
ArrayList list = new ArrayList<>();
list.add(2);

Thread thread1 = new Thread(){
    public void run() {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            iterator.remove();
        }
    };
};

Thread thread2 = new Thread(){
    public void run() {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            iterator.remove();
        }
    };
};

上記のコードは反復器のremove法を用いてlist構造を変更したが,上記のソースコード解析からremove法内部はスレッドが安全ではなく,マルチスレッドが同時にremoveする場合でもjava.util.ConcurrentModificationExceptionを引き起こす可能性があることが分かった.したがって、マルチスレッド同期更新ArrayListはできるだけ避けるべきであり、synchronizedをロックすることで同期更新の正常な動作を保証することもできる.