【Java】ConcurrentModificationExceptionについて

【Java】ConcurrentModificationExceptionについて

JavaのConcurrentModificationExceptionは、コレクションが予期しない変更を受けたときにスローされる例外です。この例外は特にマルチスレッド環境やコレクションの反復処理中に頻繁に発生します。以下では、この例外の概要、原因、回避策について詳しく解説し、具体例を多く取り上げます。

ConcurrentModificationExceptionとは

ConcurrentModificationExceptionは、コレクションの構造が予期しない形で変更された場合に発生するランタイム例外です。この例外は主に以下の状況でスローされます。

  • 1つのスレッドでコレクションを反復処理中に、別のスレッドがそのコレクションを変更した場合。
  • 同じスレッド内で、IteratorListIteratorを使用して反復処理している最中に、コレクションを直接変更した場合。

この例外は、Iteratorによって内部的に保持されているモディファイカウント(modCount)というフィールドが、反復処理中に変更されたことを検知してスローされます。

原因の詳細を見る | 具体例を見る | 回避策を見る

ConcurrentModificationExceptionの主な原因

この例外の原因として、次のような状況が挙げられます。

コレクションの直接変更

コレクションを反復処理中にaddremove、またはclearなどを直接呼び出すと、ConcurrentModificationExceptionが発生します。

マルチスレッド環境でのアクセス

複数のスレッドが同時に同じコレクションにアクセスし、一方が反復処理を行い、他方が変更操作を行った場合、例外がスローされることがあります。

概要に戻る | 具体例を見る | 回避策を見る

ConcurrentModificationExceptionの具体例

例1: コレクションの直接変更


import java.util.ArrayList;
import java.util.Iterator;

public class Example1 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        for (String item : list) {
            if (item.equals("B")) {
                list.remove(item); // ここでConcurrentModificationExceptionが発生
            }
        }
    }
}
    

例2: マルチスレッド環境


import java.util.ArrayList;

public class Example2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Thread t1 = new Thread(() -> {
            for (String item : list) {
                System.out.println(item);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            list.add("D"); // 反復処理中に変更
        });

        t1.start();
        t2.start();
    }
}
    

例3: Iteratorを使用していない場合


import java.util.ArrayList;

public class Example3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if (item.equals("B")) {
                list.add("D"); // ConcurrentModificationExceptionが発生
            }
        }
    }
}
    

概要に戻る | 原因を見る | 回避策を見る

ConcurrentModificationExceptionの回避策

1. Iteratorのremoveメソッドを使用

コレクションの要素を削除する場合、Iteratorremoveメソッドを使用することで例外を防げます。


import java.util.ArrayList;
import java.util.Iterator;

public class Solution1 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if (item.equals("B")) {
                iterator.remove(); // 安全に要素を削除
            }
        }

        System.out.println(list); // [A, C]
    }
}
    

2. 同期化されたコレクションを使用

Collections.synchronizedListでコレクションを同期化することで、マルチスレッド環境での例外を防ぐことができます。


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Solution2 {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        list.add("A");
        list.add("B");
        list.add("C");

        synchronized (list) {
            for (String item : list) {
                System.out.println(item);
            }
        }
    }
}
    

3. CopyOnWriteArrayListの使用

スレッドセーフなコレクションであるCopyOnWriteArrayListを使用すると、反復処理中に安全に変更できます。


import java.util.concurrent.CopyOnWriteArrayList;

public class Solution3 {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        for (String item : list) {
            if (item.equals("B")) {
                list.add("D"); // 例外が発生しない
            }
        }

        System.out.println(list); // [A, B, C, D]
    }
}
    

概要に戻る | 原因を見る | 具体例を見る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です