3

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

上記は、javadocからのConcurrentModificationException定義です。

だから私は以下のコードをテストしようとします:

final List<String> tickets = new ArrayList<String>(100000);
for (int i = 0; i < 100000; i++) {
    tickets.add("ticket NO," + i);
}
for (int i = 0; i < 10; i++) {
    Thread salethread = new Thread() {
        public void run() {
            while (tickets.size() > 0) {
                tickets.remove(0);
                System.out.println(Thread.currentThread().getId()+"Remove 0");
            }
        }
    };
    salethread.start();
}

コードは単純です。10スレッドで、arraylistオブジェクトから要素が削除されます。複数のスレッドが1つのオブジェクトにアクセスすることは確実です。しかし、それは問題なく動作します。例外はスローされません。なんで?

4

5 に答える 5

7

私はArrayListあなたの利益のためにJavadocの大部分を引用しています。表示されている動作を説明する関連部分が強調表示されます。

この実装は同期されていないことに注意してください。複数のスレッドが同時にArrayListインスタンスにアクセスし、少なくとも1つのスレッドがリストを構造的に変更する場合は、外部で同期する必要があります。(構造変更とは、1つ以上の要素を追加または削除する操作、またはバッキング配列のサイズを明示的に変更する操作です。要素の値を設定するだけでは構造変更にはなりません。)これは通常、自然にカプセル化するオブジェクトを同期することで実現されます。リスト。そのようなオブジェクトが存在しない場合は、Collections.synchronizedListメソッドを使用してリストを「ラップ」する必要があります。これは、リストへの偶発的な非同期アクセスを防ぐために、作成時に行うのが最適です。

リストリスト=Collections.synchronizedList(new ArrayList(...));

このクラスのイテレータメソッドとlistIteratorメソッドによって返されるイテレータはフェイルファストです。イテレータの作成後にリストが構造的に変更された場合、イテレータ自体のremoveまたはaddメソッド以外の方法で、イテレータはConcurrentModificationExceptionをスローします。したがって、同時変更に直面した場合、イテレータは、将来の不確定な時点で任意の非決定的な動作のリスクを冒すのではなく、迅速かつクリーンに失敗します。

イテレータのフェイルファスト動作は保証できないことに注意してください。一般的に言えば、同期されていない同時変更が存在する場合、ハード保証を行うことは不可能です。フェイルファストイテレータは、ベストエフォートベースでConcurrentModificationExceptionをスローします。したがって、その正確性をこの例外に依存するプログラムを作成するのは誤りです。イテレータのフェイルファスト動作は、バグを検出するためにのみ使用する必要があります。

ArrayListsは、イテレータを介してリストにアクセスしているときにリストを構造的に変更すると、通常、同時変更例外をスローします(ただし、これでも絶対的な保証ではありません)。この例では、リストから要素を直接削除しており、イテレータを使用していないことに注意してください。

それがあなたの空想をくすぐるなら、それArrayList.removeがどのように機能するかをよりよく理解するために、の実装を閲覧することもできます。

于 2013-02-19T02:50:52.357 に答える
2

この場合、「並行」はスレッド関連を意味するとは思わないか、少なくとも必ずしもそれを意味するとは限りません。ConcurrentModificationExceptions は通常、反復処理中にコレクションを変更することで発生します。

List<String> list = new ArrayList<String>();
for(String s : list)
{
     //modifying list results in ConcurrentModificationException
     list.add("don't do this");     

}

このIterator<>クラスには、これを回避できるメソッドがいくつかあることに注意してください。

for(Iterator it = list.iterator(); it.hasNext())
{
     //no ConcurrentModificationException
     it.remove(); 
}
于 2013-02-19T02:53:57.347 に答える
1

イテレータを使用していないため、ConcurrentModificationExceptionスローされる可能性はありません。

呼び出すremove(0)と、最初の要素が削除されます。実行が完了する前に別のスレッドが0を削除した場合、呼び出し元が意図したものと同じ要素ではない可能性があります。

于 2013-02-19T02:59:06.010 に答える
1

あなたが受け取っていない理由ConcurrentModificationExceptionはそれをArrayList.remove投げないからです。おそらく、配列を反復処理する追加のスレッドを開始することで取得できます。

final List<String> tickets = new ArrayList<String>(100000);
for (int i = 0; i < 100000; i++) {
    tickets.add("ticket NO," + i);
}
for (int i = 0; i < 10; i++) {
    Thread salethread = new Thread() {
        public void run() {
            while (tickets.size() > 0) {
                tickets.remove(0);
                System.out.println(Thread.currentThread().getId()+"Remove 0");
            }
        }
    };
    salethread.start();
}
new Thread() {
    public void run() {
        int totalLength = 0;
        for (String s : tickets) {
            totalLength += s.length();
        }
    }
}.start();
于 2013-02-19T02:53:17.000 に答える
1

しかし、それは問題なく実行されます。例外はスローされません。なんで?

その同時変更が許可されているという理由だけで。

例外の説明には次のように書かれています。

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

明確な意味は、同時変更が許容される (または許容される可能性がある) ことです。実際、標準の Java の非並行コレクション クラスの場合、並行変更が許可されます。ただし、反復中に変更が発生しない場合に限られます。


この背後にある理由は、非並行コレクションの場合、反復中の変更は基本的に安全でなく、予測できないためです。正しく同期したとしても (そしてそれは簡単ではありません1 )、結果は予測できません。Java 1.1 コレクション クラスを使用するマルチスレッド アプリケーションでは、これが Heisenbugs の一般的な原因であったため、同時変更の「フェイルファスト」チェックが通常のコレクション クラスに含まれていました。

1- たとえば、「synchronizedXxx」ラッパー クラスは反復子と同期できません。問題は、繰り返しが と の呼び出しを交互next()hasNext()行うことであり、他のスレッドを除外しながらメソッド呼び出しのペアを実行する唯一の方法は、外部同期を使用することです。ラッパー アプローチは、Java では実用的ではありません。

于 2013-02-19T03:26:21.047 に答える