【Java】ConcurrentModificationExceptionについて
JavaのConcurrentModificationException
は、コレクションが予期しない変更を受けたときにスローされる例外です。この例外は特にマルチスレッド環境やコレクションの反復処理中に頻繁に発生します。以下では、この例外の概要、原因、回避策について詳しく解説し、具体例を多く取り上げます。
ConcurrentModificationExceptionとは
ConcurrentModificationException
は、コレクションの構造が予期しない形で変更された場合に発生するランタイム例外です。この例外は主に以下の状況でスローされます。
- 1つのスレッドでコレクションを反復処理中に、別のスレッドがそのコレクションを変更した場合。
- 同じスレッド内で、
Iterator
やListIterator
を使用して反復処理している最中に、コレクションを直接変更した場合。
この例外は、Iterator
によって内部的に保持されているモディファイカウント(modCount)というフィールドが、反復処理中に変更されたことを検知してスローされます。
ConcurrentModificationExceptionの主な原因
この例外の原因として、次のような状況が挙げられます。
コレクションの直接変更
コレクションを反復処理中にadd
、remove
、または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
メソッドを使用
コレクションの要素を削除する場合、Iterator
のremove
メソッドを使用することで例外を防げます。
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]
}
}