異常記録——java.util.ConcurrentModificationException


先日のプロジェクト統合の時、サーバーで私が担当している機能をテストした時、Exceptionが出てきましたが、これまでローカルでテストした時にテストされていませんでした.異常情報:java.util.ConcurrentModificationException
ボスが一目で分かるようになったのは、リスト集合を巡る際に、ArrayListのremove(Object o)メソッドを使ったということです.次は、例外が発生したことをテストする方法です.
package com.day0219.list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author xukai
 *  List , remove(Object o) 
 */
public class TestList {
	
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		//  
		for(int i = 1; i < 100; i++){
			list.add("str_" + i);
		}
		
		//  foreach List, remove 
		for(String str : list){
			//  remove
			if(Integer.parseInt(str.substring(4)) % 2 == 0){
				list.remove(str);
			}
		}
		
		//  Iterator List
		Iterator<String> iterator = list.iterator();
		while(iterator.hasNext()){
			String str = iterator.next();
			if(Integer.parseInt(str.substring(4)) % 2 == 0){
				list.remove(str);
			}
		}
		
		//  List
		for(String str : list){
			System.out.println(list.indexOf(str) + "," + str);
		}
		
	}

}
ここでは、2つのループ集合の方法が使用されている.
前者はjdk 5である.0新しく追加されたループ構造は、集合の下付きを考慮することなく、集合内の各要素を処理するために使用できます.フォーマットは次のとおりです.
for(vaiable : collection) { statement; }
変数を定義して、セット内の各要素を一時保存し、対応する文ブロックを実行します.Collectionは配列またはIterableインタフェースを実装したクラスオブジェクトでなければなりません.
後者はインタフェースIterableを使用して、このインタフェースがオブジェクトを「foreach」文のターゲットにすることを可能にする.メソッドiterator()は、Tタイプの要素のセット上で反復する反復器を返します.
どちらもremove(Object o)メソッドを使用する場合に異常javaが発生する.util.ConcurrentModificationException.後者の分析によれば、分かりやすい.
異常が発生した場所:
iterator()メソッドは、ArrayListの親AbstractListで実装されていることがわかります.
    public Iterator<E> iterator() {
        return new Itr();
    }
内部クラス:
    private class Itr implements Iterator<E> {

        int cursor = 0;

        int lastRet = -1;

        int expectedModCount = modCount;

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

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

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

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
フィールドの説明:
    protected transient int modCount = 0;
このリストを構造から変更した回数.構造的に変更とは、リストのサイズを変更したり、リストを乱したりして、進行中の反復にエラーの結果を生じさせることです.このフィールドはiteratorおよびlistIteratorメソッドが返す反復器およびリスト反復器の使用.このフィールドの値を誤って変更した場合、反復器(またはリスト反復器)は放出されます.ConcurrentModificationException応答nextremoveprevioussetまたはadd操作.反復中に同時変更に直面した場合、
非確定的な行為ではなく、迅速な失敗行為. 
以上がAPI中国語翻訳、すなわち異常発生の原因である.
次に、例外が発生するプロセスを示します.
list.iterator()/Itr()クラスオブジェクトを作成し、データを初期化
cursor = 0;//カーソル
lastRet = -1;//前のインデックスの場所
expectedModCount = 0;//反復器は、後のリストにmodCountの値があるべきだと考えています.この予想が破壊されると、反復器は同時修正を検出します.
iterator.hasNext();//カーソル位置が集合サイズに等しいかどうかを判断し、falseを返す
iterator.next();//次へ移動
checkForComodification()を実行します.
list.remove(Object o);
この方法の実装:
    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;
    }
は実際にfastRemove(index)として実行される.
    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
    }
はまずmodCount++を見ることができて、値は変化して、最後に削除した要素のメモリアドレスをNULLに設定して、GCの回収を便利にします.
次の実行
next()メソッドの場合、checkForComodification()の判断:
if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
異常放出.
foreachループは実際にIteratorによって実現されるため、foreachループを用いて集合を巡る原理は同じである.同様にnext()メソッドを実行して検証を行い、同じ異常が発生します.
解決:iteratorを使用します.remove()メソッド
Itr()内部メソッド
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

ここではexpectedModCount=modCountを実行する.異常発生の防止
		while(iterator.hasNext()){
			String str = iterator.next();
			if(Integer.parseInt(str.substring(4)) % 2 == 0){
				iterator.remove();	//  
			}
		}

foreachループを使用してループし、削除する場合は、リストセットを再作成してマウントできます.
public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		//  
		for(int i = 1; i < 100; i++){
			list.add("str_" + i);
		}
		List<String> temp = new ArrayList<>();
		//  foreach List, remove 
		for(String str : list){
			//  remove
			if(Integer.parseInt(str.substring(4)) % 2 != 0){
				temp.add(str);
			}
		}
		list = null;// GC 
		
		//  List
		for(String str : temp){
			System.out.println(temp.indexOf(str) + "," + str);
		}
		
	}

*注意:上記のテストはすべて単一スレッド環境で行われています.