MeWrite Docs

ConcurrentModificationException

イテレーション中にコレクションが変更された場合に発生するJavaの例外

概要

ConcurrentModificationExceptionは、コレクションのイテレーション中にそのコレクションの構造が変更された場合に発生します。シングルスレッドでもfor-eachループ内での要素追加・削除で頻繁に発生します。

エラーメッセージ

Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
    at com.example.MyClass.process(MyClass.java:25)

原因

  1. for-eachループ内での要素削除: 拡張forループの反復中にremove()add()を呼び出している
  2. マルチスレッドでの同時アクセス: 一方のスレッドがイテレーション中に、別スレッドがコレクションを変更している
  3. Stream処理中のソース変更: Streamのソースとなるコレクションを処理中に変更している

解決策

1. Iterator.remove()を使用する

イテレーション中の削除にはIteratorremove()メソッドを使う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

// NG: for-eachループ内で削除
for (String item : list) {
    if (item.equals("b")) {
        list.remove(item); // ConcurrentModificationException
    }
}

// OK: Iteratorを使用
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.equals("b")) {
        it.remove(); // 安全に削除
    }
}

2. removeIf()を使用する(Java 8以降)

条件に一致する要素を安全に削除できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

// 条件に一致する要素を一括削除
list.removeIf(item -> item.equals("b"));

// Mapの場合
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.entrySet().removeIf(entry -> entry.getValue() < 3);

3. ConcurrentHashMapを使用する

マルチスレッド環境ではスレッドセーフなコレクションを使う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// NG: HashMapはスレッドセーフではない
Map<String, Integer> unsafeMap = new HashMap<>();

// OK: ConcurrentHashMapを使用
Map<String, Integer> safeMap = new ConcurrentHashMap<>();
safeMap.put("key1", 1);
safeMap.put("key2", 2);
safeMap.put("key3", 3);

// イテレーション中の変更も安全(弱い一貫性)
for (Map.Entry<String, Integer> entry : safeMap.entrySet()) {
    if (entry.getValue() < 2) {
        safeMap.remove(entry.getKey());
    }
}

4. CopyOnWriteArrayListを使用する

読み取りが多く書き込みが少ない場合に有効。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 書き込み時にコピーを作成するため、イテレーション中の変更が安全
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));

for (String item : list) {
    if (item.equals("b")) {
        list.remove(item); // 例外は発生しない
    }
}

// 注意: 書き込みのたびにコピーが作成されるため、
// 書き込みが頻繁な場合はパフォーマンスに影響する

5. 新しいリストを作成する

削除対象を収集してから一括で処理する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

// 削除対象を別リストに収集
List<String> toRemove = new ArrayList<>();
for (String item : list) {
    if (item.equals("b") || item.equals("c")) {
        toRemove.add(item);
    }
}
list.removeAll(toRemove);

関連エラー

Java の他のエラー

最終更新: 2026-02-03