JavaのConcurrentModificationExceptionとは
Javaでコレクション(リスト、セット、マップなど)を操作する際に、イテレーション中にコレクションが変更されるとConcurrentModificationExceptionが発生します。
これは、イテレータがコレクションを走査している間に他のスレッドからそのコレクションが変更されると、予期せぬ結果を招く可能性があるため、Javaではこれを防ぐための仕組みとしてConcurrentModificationExceptionが設けられています。
Javaのエラー一覧はコチラ
-
【Java】よく発生するエラー一覧12選 エラーの発生事例と対処方法をみてみよう!
Javaのエラーとその対処方法 この記事では、Javaでよく発生するエラーと各エラーが起きる事例と、その対処方法を紹介していきます。 下記のエラーについてみていきます! エラーリスト NullPoin ...
続きを見る
エラーケース1: イテレーション中のリスト変更
最も一般的な例は、for-eachループを使用してリストをイテレートしながらリスト自体を変更しようとする場合です。
以下にそのようなソースコードを示します。
エラーが発生するソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); for (String item : list) { if ("Two".equals(item)) { list.remove(item); } } } } |
出力結果
Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996) at Main.main(Main.java:11)
このソースコードでは、リストをイテレートしながらリストから要素を削除しようとしています。
この操作は、イテレータがリストの構造を保証できないため、ConcurrentModificationExceptionを引き起こします。
対処法1: Iteratorを使用する
この問題を解決する一つの方法は、Iteratorを使用することです。
Iteratorのremoveメソッドを使用すれば、イテレーション中に安全にコレクションを変更することができます。以下に修正後のソースコードを示します。
対処後のソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("Two".equals(item)) { iterator.remove(); } } } } |
出力結果
(エラーなし)
このソースコードでは、Iteratorのremoveメソッドを使用してリストから要素を削除しています。
Iteratorのremoveメソッドは、イテレータが指している要素を安全に削除します。
したがって、このコードはConcurrentModificationExceptionを引き起こしません。
エラーケース2: マルチスレッド環境
マルチスレッド環境では、一つのスレッドがコレクションをイテレートしている間に、別のスレッドがコレクションを変更するとConcurrentModificationExceptionが発生します。
以下にそのようなソースコードを示します。
エラーが発生するソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); new Thread(() -> { for (String item : list) { System.out.println(item); } }).start(); new Thread(() -> { list.add("Four"); }).start(); } } |
出力結果
Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996) at Main.lambda$main$0(Main.java:12) at java.base/java.lang.Thread.run(Thread.java:834)
このソースコードでは、一つのスレッドがリストをイテレートしている間に、別のスレッドがリストに新しい要素を追加しています。
これにより、イテレーション中にリストの構造が変わるため、ConcurrentModificationExceptionが発生します。
KindleUnlimited会員であれば、全ての本をご覧頂けます。 StreamAPIを理解すれば、Javaの世界が変わる 第1版
対処法2: 同期化されたコレクションを使用する
この問題を解決する方法の一つは、同期化されたコレクションを使用することです。
同期化されたコレクションは、一度に一つのスレッドのみがアクセスできるようにロックを提供します。
Javaでは、CollectionsクラスのsynchronizedListメソッドを使用して同期化されたリストを作成することができます。
以下に修正後のソースコードを示します。
対処後のソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Main { public static void main(String[] args) { List list = Collections.synchronizedList(new ArrayList<>()); list.add("One"); list.add("Two"); list.add("Three"); new Thread(() -> { synchronized(list) { for (String item : list) { System.out.println(item); } } }).start(); new Thread(() -> { list.add("Four"); }).start(); } } |
出力結果
(エラーなし)
このソースコードでは、Collections.synchronizedListメソッドを使用して同期化されたリストを作成しています。
また、イテレーションは同期化されたブロック内で行われ、これによりリストに対する同時アクセスが防がれます。
したがって、このコードはConcurrentModificationExceptionを引き起こしません。
エラーケース3: Stream APIと並列処理
Java 8のStream APIを使用して並列処理を行う際にも、注意が必要です。
以下のソースコードは、並列ストリームを使用してリストの各要素を削除しようとしています。
しかし、これはConcurrentModificationExceptionを引き起こします。
エラーが発生するソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); list.parallelStream().forEach(list::remove); } } |
出力結果
Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996) at java.base/java.util.AbstractCollection.remove(AbstractCollection.java:305) at Main.lambda$main$0(Main.java:10) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658) at Main.main(Main.java:10)
このソースコードでは、parallelStreamメソッドを使用してリストの並列ストリームを取得し、forEachメソッドを使用して各要素を削除しようとしています。
しかし、並列ストリームを使用すると、複数のスレッドがリストを同時に変更しようとするため、ConcurrentModificationExceptionが発生します。
対処法3: ストリーム操作を適切に使用する
この問題を解決するには、ストリーム操作を適切に使用する必要があります。
具体的には、forEachメソッドではなく、filterメソッドとcollectメソッドを使用して、削除したい要素を除外する新しいリストを作成することができます。
以下に修正後のソースコードを示します。
対処後のソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); list = list.parallelStream().filter(e -> !"Two".equals(e)).collect(Collectors.toList()); } } |
出力結果
(エラーなし)
このソースコードでは、filterメソッドを使用して"Two"と等しくない要素だけを含む新しいリストを作成しています。
この方法であれば、リストの変更が同時に行われることはなく、ConcurrentModificationExceptionを回避することができます。
エラーケース4: Iteratorのremoveメソッド
Javaでは、Iteratorを使用してコレクションを反復処理することができます。
しかし、Iteratorのremoveメソッドを使用して要素を削除する際にも、注意が必要です。
以下のソースコードは、Iteratorのremoveメソッドを使用せずにリストの要素を削除しようとしています。
これはConcurrentModificationExceptionを引き起こします。
エラーが発生するソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("Two".equals(item)) { list.remove(item); } } } } |
出力結果
Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996) at Main.main(Main.java:13)
このソースコードでは、Iteratorを使用してリストを反復処理し、"Two"と等しい要素をリストから直接削除しようとしています。
しかし、これはリストの構造を変更するため、ConcurrentModificationExceptionが発生します。
対処法4: Iteratorのremoveメソッドを使用する
この問題を解決するには、Iteratorのremoveメソッドを使用する必要があります。
Iteratorのremoveメソッドは、反復処理中のコレクションから安全に要素を削除するためのものです。
以下に修正後のソースコードを示します。
対処後のソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("Two".equals(item)) { iterator.remove(); } } } } |
出力結果
(エラーなし)
このソースコードでは、Iteratorのremoveメソッドを使用して"Two"と等しい要素を安全に削除しています。
この方法であれば、反復処理中にリストの構造を変更しても、ConcurrentModificationExceptionを回避することができます。
エラーケース5: サブリストを変更する
Javaでは、ListのsubListメソッドを使用してリストの一部を切り出すことができます。
しかし、このサブリストを変更すると、元のリストも影響を受けます。
そして、元のリストとサブリストを同時に変更しようとすると、ConcurrentModificationExceptionが発生します。
以下のソースコードは、サブリストを変更しようとしています。
エラーが発生するソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); List subList = list.subList(1, 2); list.remove(1); subList.clear(); } } |
出力結果
Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1351) at java.base/java.util.ArrayList$SubList.clear(ArrayList.java:1296) at Main.main(Main.java:12)
このソースコードでは、subListメソッドを使用して元のリストからサブリストを作成し、その後で元のリストとサブリストを同時に変更しようとしています。
これにより、リストの構造が同時に変更されるため、ConcurrentModificationExceptionが発生します。
対処法5: サブリストを新しいリストとして作成する
この問題を解決するには、サブリストを新しいリストとして作成する必要があります。
これにより、サブリストの変更が元のリストに影響を及ぼすことはありません。
以下に修正後のソースコードを示します。
対処後のソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List list = new ArrayList<>(); list.add("One"); list.add("Two"); list.add("Three"); List subList = new ArrayList<>(list.subList(1, 2)); list.remove(1); subList.clear(); } } |
出力結果
(エラーなし)
このソースコードでは、新しいArrayListのインスタンスを作成してサブリストを生成しています。
これにより、サブリストの変更が元のリストに影響を及ぼさないため、ConcurrentModificationExceptionを回避できます。
まとめ
Javaでリストを反復処理中にリストの構造を変更しようとすると、ConcurrentModificationExceptionが発生する可能性があります。
このエラーを回避するためには、反復処理とリストの構造の変更を分離するか、Iteratorのremoveメソッドや、Stream APIのfilterメソッドなど、反復処理中にリストの構造を安全に変更する方法を使用する必要があります。
また、サブリストを変更する場合は、新しいリストとして作成することで元のリストに影響を及ぼさないようにすることが重要です。
これらの方法を理解し、適切に使用することで、Javaのリスト操作をより安全に、効率的に行うことができます。
エラーが出た時は、ここで学んだ方法を思い出して対処してみてほしいな!
最後まで読んで頂き、ありがとうございました。少しでもお役にたてたなら幸いです!
Javaのエラー一覧はコチラ
-
【Java】よく発生するエラー一覧12選 エラーの発生事例と対処方法をみてみよう!
Javaのエラーとその対処方法 この記事では、Javaでよく発生するエラーと各エラーが起きる事例と、その対処方法を紹介していきます。 下記のエラーについてみていきます! エラーリスト NullPoin ...
続きを見る