ConcurrentModficationExceptionの対処法

スレッドでArrayListListなどでイテレータを使って要素を変更したり、削除したりしようとするとこのConcurrentModficationExceptionが発生することがあります。

ゲームなどではスレッドを使って数ミリ秒ごとに内容を更新するので、このエラーを見かけることは多いかもしれません。。

そもそもこのエラーは何かというと公式リファレンスではこう書いてあります。

この例外は、オブジェクトの並行変更を検出したメソッドによって、そのような変更が 許可されていない場合にスローされます。 たとえば、あるスレッドが Collection で繰り返し処理を行なっている間に、 別のスレッドがその Collection を変更することは一般に許可されません。 通常、そのような環境では、繰り返し処理の結果は保証されません。

難しそうなことが書いてありますが、あるメソッド内でアイテムをループさせている途中でアイテムを変更(削除または追加)するなという意味だと思います。(もしかしたら間違っているかも...)

では、実際にどんな時にエラーが出るかというと次のような場合です。

List<String> fruits = new ArrayList<String>()
{
    {
        add("Apple"); add("Banana"); add("Grape");
    }
};

Iterator<String> it = fruits.iterator();
while(it.hasNext){
    if(it.next().equals("Banana")) 
        fruits.remove(1);
}

イテレータでループを回している最中に要素を消しているのでリストのサイズが変わってしまうのでConcurrentModificationExceptionが発生します。

同様にfor-each文もイテレータを使うので同じ例外が発生します。

for(String fruit : fruits){
    if(fruit.equals("Banana")) 
        fruits.remove(1);
}

では、どうすればいいかというと1つめの対策としてイテレータを使わないという方法があります。次のようなコードです。

int size = fruits.size();
for(int i = 0; i < fruits.size(); ++i){
    if(fruits.get(i).equals("Banana")) fruits.remove(1);
}

これなら、要素の数が変わっても対応できます。

もう1つはCollections.synchronizedListを使う方法です。次のようにリストをこのクラスでラップさせます。

List<String> fruits = Collections.synchronizedList(new ArrayList());

そして以下のコードのようにsynchronizedでリストをロックして同期をとることで安全に要素にアクセスできます。

synchronized(fruits){
    Iterator it = fruits.iterator();
    while(it.hasNext()){
        if(it.next().equals("Banana")) 
            it.remove();
    }
}

ちなみにsynchronizedListはメソッドの外側で宣言しなくてはいけません。そうしないとやはりConcurrentModificationExceptionが発生してしまいます。

以上、ConcurrentModificationExceptionについてまとめてみました。では、また!

関連項目
プライバシーポリシー