web-dev-qa-db-ja.com

同時変更例外

この小さなコードがあり、同時変更の例外が発生します。同時変更が実行されていないのに、なぜそれを取得し続けるのか理解できません。

import Java.util.*;

public class SomeClass {
    public static void main(String[] args) {
        List<String> s = new ArrayList<>();
        ListIterator<String> it = s.listIterator();

        for (String a : args)
            s.add(a);

        if (it.hasNext())
            String item = it.next();

        System.out.println(s);
    }
}
43
Ankit

ConcurrentModificationExceptionを回避するには、次のようにコードを記述する必要があります。

import Java.util.*;

public class SomeClass {

    public static void main(String[] args) {
        List<String> s = new ArrayList<String>();

        for(String a : args)
            s.add(a);

        ListIterator<String> it = s.listIterator();    
        if(it.hasNext()) {  
            String item = it.next();   
        }  

        System.out.println(s);

    }
}

Java.util.ListIteratorを使用すると、反復中にリストを変更できますが、作成から使用までの間は変更できません。

51
Pascal Thivent

同時変更が実行されていないのに、なぜそれを取得し続けるのか理解できません。

反復子を作成してから反復子の使用を開始するまでの間に、反復するリストに引数を追加しました。これは同時変更です。

    ListIterator<String> it = s.listIterator();  

    for (String a : args)
        s.add(a);                    // concurrent modification here

    if (it.hasNext())
        String item = it.next();     // exception thrown here

リストへの要素の追加が終了したら、イテレータを作成します。

    for (String a : args)
        s.add(a); 

    ListIterator<String> it = s.listIterator();  
    if (it.hasNext())
        String item = it.next();
44
Stephen C

ConcurrentModificatoinExceptionの JavaDoc: から:「通常、あるスレッドがコレクションを変更している間に別のスレッドがコレクションを変更することは許可されていません」。

それは単に、開いているイテレータがまだある場合、イテレータループが壊れるのでリストを変更できないことを意味します。 forループの後までListIterator<String> it = s.listIterator();を移動してみてください。

11
leonm

基になるリストが変更された後は、イテレータの反復を続けることはできません。ここでは、いくつかのアイテムをsに追加する前にイテレータを作成し、追加後にhasNext()next()を実行して、ConcurrentModificationException

8
Yuliy

上記の解決策が適切に機能しない場合。古いforループを使用して、新しいアイテムを追加すると同時にListを反復できます。 以下の例を参照してください:

import Java.util.*;

public class SomeClass {
    public static void main(String[] args) {
        ArrayList<AClass> aList = new ArrayList<AClass>(); // we will iterate this


        // this will cause ConcurrentModificationException. 
        // Since we are iterating the list, at the same time modifying it.
        /*for(AClass a: aList){
           aList.add(someMethod(a));
        }*/

        // old fashion for-loop will help
        int limit = aList.size();
        for(int i=0; ctr<limit; ++i){
           AClass a = aList.get(i);
           aList.add(someMethod(a));
        }


    }
}
5
jzarsuelo

ConcurrentModificationExceptionは、シングルスレッド環境とマルチスレッド環境の両方で発生する可能性があります。主な問題は、すべての汎用イテレータ(ArrayListで使用されるイテレータなど)がすべてFailFastイテレータであり、1つのリストを変更しようとすると失敗することです。 1つのイテレーターが既にそれを反復している場合。解決策-> ArrayListを使用するのではなく、要件によってこのようなシナリオが必要な場合は、CopyOnWriteArrayListを使用します。

このための完全なデモについては、下記のコードを使用できます。実装をCopyOnWriteArrayListからArrayListに変更するだけです。

import Java.util.ArrayList;
import Java.util.Iterator;
import Java.util.List;
import Java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author narif
 *
 */
public class TestApp {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> testList = new ArrayList<>();
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add("abc");
        testList.add(6, "abcAtindex6");
        int size = testList.size();
        System.out.println("The Current List (ArrayList) is: " + testList);
        System.out.println("The size of the List (ArrayList) is: " + size);
        /* Comment the below lines to get the ConcurrentModificationException */
        testList = new CopyOnWriteArrayList<>(testList);
        for (String value : testList) {
            System.out.println("The Value from ForEach Loop is: " + value);
            /*
             * Concurrent modification is happening here
             * One iterator is iterating over the list while we are trying to add new values to
             * the list so the results of the iteration are undefined under these circumstances.
             * So teh fail fast iterators will fail and will throw the ConcurrentModificationException.
             */
            testList.add("valueFromForLoop");
            testList.add("anotherValueFromForEachLoop");
        }
        Iterator<String> it = testList.iterator();
        while (it.hasNext()) {
            String abc = it.next();
            System.out.println(abc);
            testList.add("Value from Iterator1");
            testList.add("Value from Iterator2");
            testList.add("Value from Iterator3");
            testList.add("Value from Iterator4");

        }
        System.out.println("Did the modificationa and all after conevrting the ArrayList to CopyOnWriteArrayList.");
        System.out.println("Calling the method to get the new List..");
        testList = new CopyOnWriteArrayList<>(getTheList(testList));
        for (String value : testList) {
            System.out.println("The value returned from method is : " + value);
        }
    }

    private static List<String> getTheList(List<String> pList) {
        List<String> list = new CopyOnWriteArrayList<>(pList);
        int i = 0;
        for (String lValue : list) {
            System.out.println("The list Passed is " + list);
            i++;
            list.add("localVaueFromMethod" + i);
            list.removeAll(pList);
        }
        return list;
    }

}

このリンクに従ってより多くのinifoを行うには、この方法が役に立つかもしれません ConcurrentModificationException Java Docs

3
Najeeb Arif

これを理解するには、HashMap実装のソースを見てみましょう。

public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable{

以下のようにHashIteratorが含まれます。

private abstract class HashIterator {
    ...
    int expectedModCount = modCount;
    ...

    HashMapEntry<K, V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        .... 
        }

イテレータを作成するたびに:

  • カウンターexpectedModCountが作成され、エントリとしてmodCountの値に設定されますチェックポイント
  • modCountは、put/get(追加/削除)を使用する場合にインクリメントされます
  • nextEntry反復子のメソッドが異なる場合、現在のmodCountでこの値をチェックしています

これを避けるために:

  • マップを配列に変換します(大きなマップにはお勧めしません)
  • 並行性マップまたはリストクラスを使用する( CopyOnWriteArrayList / ConcurrentMap
  • ロックマップ(このアプローチはマルチスレッドの利点を取り除きます)

これにより、例外を発生させることなく、要素の反復と追加または削除を同時に行うことができます

同時実行マップ/リストイテレータは、ConcurrentModificationExceptionをスローしない「弱整合性」イテレータであり、イテレータの構築時に存在した要素をトラバースすることを保証し、構築後の変更を反映する場合があります(保証されません)。

CopyOnWriteArrayListの詳細

1
ceph3us

これは機能しませんでした:

LinkedList<String> linkedList = new LinkedList<String>();
ListIterator listIterator = linkedList.listIterator();
linkedList.add("aa");
linkedList.add("bb");

これはうまくいきました:

LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("aa");
linkedList.add("bb");
ListIterator listIterator = linkedList.listIterator();
1
Nurgul Alshyn

Oracle ドキュメント ページをご覧ください。

public class ConcurrentModificationException
extends RuntimeException

この例外は、オブジェクトの同時変更が許可されていないときに、その変更を検出したメソッドによってスローされる場合があります

この例外は、オブジェクトが別のスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。 1つのスレッドがオブジェクトのコントラクトに違反する一連のメソッド呼び出しを発行すると、オブジェクトはこの例外をスローする場合があります。 たとえば、スレッドがフェイルファーストイテレーターでコレクションを反復処理している間にコレクションを直接変更すると、イテレーターはこの例外をスローします

あなたの場合、イテレータを作成した後にコレクションを変更したため、例外が発生しました。

Stephen C回答に従ってコードを変更しても、このエラーは発生しません。

1
Ravindra babu