極楽とんぼのロボット製作記

情報工学系大学院生がロボットとその周辺技術や身の回りの出来事について紹介するブログ

JavaのLinkedListやArrayListを使用中「ConcurrentModificationException」というエラーが出る

ProcessingにてLinkedListを使用した際に出くわしたエラーです。

エラーが出た状況

まずはソースコードをお見せします。

import java.util.LinkedList;
class Hoge {
  int state;
  
  Hoge(){
    state = 0;
  }
  
  int is_state(){
    return state;
  }
}

int hogeNum = 5;

LinkedList<Hoge> hogeList;

void setup(){
  hogeList = new LinkedList<Hoge>();
  for(int i=0; i<hogeNum; i++){
    hogeList.add(new Hoge());
  }
}

void draw(){
  int listNum = 0;
  for(Hoge h : hogeList) {
      if(h.is_state() == 0){
        hogeList.remove(listNum);
      }
    listNum++;
  }
  
}

原因

「ConcurrentModificationException」で調べて見るとOracleのレファレンスページでは以下のように記述されている

以下 ConcurrentModificationException (Java Platform SE 7) からの引用

public class ConcurrentModificationException
extends RuntimeException
この例外は、オブジェクトの並行変更を検出したメソッドによって、そのような変更が許可されていない場合にスローされます。
たとえば、あるスレッドが Collection で繰り返し処理を行なっている間に、別のスレッドがその Collection を変更することは一般に許可されません。通常、そのような環境では、繰り返し処理の結果は保証されません。いくつかのイテレータ (Iterator) の実装 (JRE が提供するすべての一般的な目的のコレクションの実装の、イテレータの実装を含む) は、その動作が検出された場合にこの例外をスローすることを選択できます。この例外をスローするイテレータは、フェイルファストイテレータと呼ばれます。イテレータは、将来の予測できない時点において予測できない動作が発生する危険を回避するために、ただちにかつ手際よく例外をスローします。

この例外は、オブジェクトが別のスレッドによって並行して更新されていないことを必ずしも示しているわけではありません。単一のスレッドが、オブジェクトの規約に違反する一連のメソッドを発行した場合、オブジェクトはこの例外をスローします。たとえば、フェイルファストイテレータを持つコレクションの繰り返し処理を行いながら、スレッドがコレクションを直接修正する場合、イテレータはこの例外をスローします。

通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、フェイルファストの動作を保証することはできません。フェイルファストオペレーションは最善努力原則に基づき、ConcurrentModificationException をスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。ConcurrentModificationException は、バグを検出するためにのみ使用してください。

言い回しがかたすぎて言ってることがさっぱり分かりませんが、「あるスレッドが Collection で繰り返し処理を行なっている間に、別のスレッドがその Collection を変更することは一般に許可されません。」に注目して、プログラムを読み返すと、

 int listNum = 0;
  for(Hoge h : hogeList) {
      if(h.is_state() == 0){
        hogeList.remove(listNum);
      }
    listNum++;
  }

Range-based forでリストを探索中にstateが0のオブジェクトを削除してしまっているため、リストの長さが変わってしまっていることがわかります。これが上で言うあるスレッドがリストの繰り返し処理を行っている間に別のスレッドがリストに変更をしてしまうことに当てはまると考えられます。


対処方法

先程のリストの削除を実行する部分を

for(int i=0; i<hogeList.size(); i++){
      if(hogeList.get(i).is_state() == 0){
        hogeList.remove(i);
        i--;
      }
}

と書き換えます。変数iでlistの走査を行い、リストが削除されると変数iがデクリメントされることで、削除された要素の次の要素にアクセスできるようにしています。

もっとカッコよくイテレータを使って削除する方法もあるようです。
memo.sugyan.com


参考サイト
ConcurrentModificationException (Java Platform SE 7)
Iteratorの中で要素を削除するということ - すぎゃーんメモ
[Java]新人向けにjava.util.ArrayListで発生するConcurrentModificationExceptionについて解説 - Qiita