コレクションの注意点
- 拡張for文を用いてremove()メソッドで要素の削除をするとConcurrentModificationException例外が発生する
- 反復処理中にコレクションの変更の可能性がある場合は、例外を投げる仕様
package cp8.no7;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "test1");
map.put(2, "test2");
map.put(3, "test3");
// 例外が発生する(ConcurrentModificationException)
for (Integer key : map.keySet()) {
map.remove(key);
}
// 順番に一つずつ削除するのは問題ない
// map.remove(1);
// map.remove(2);
// map.remove(3);
}
}
実行結果
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493)
at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1516)
at cp8.no7.Main.main(Main.java:16)
マルチスレッド環境下におけるMapインターフェイスの拡張
- ConcurrentMapインターフェイスのメソッドを利用する
- putIfAbsent()メソッド→1回のロックでcontainsKey()メソッドで確認後、put()メソッドで格納するなど2つの処理を行える
package cp8.no9;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "test1");
map.put(2, "test2");
map.put(3, "test3");
for (Integer key : map.keySet()) {
map.remove(key);
}
System.out.println("削除完了");
}
}
実行結果
削除完了
ArrayListクラスとSetインターフェイスの拡張
- CopyOnWriteArrayListクラス→配列のコピーを作成し、スレッドセーフを実現する
- CopyOnWriteArraySetクラス→内部的にはCopyOnWriteArrayListを使用して、スレッドセーフを実現する
ArrayListにおいてConcurrentModificationExceptionが発生する例
package cp8.no10;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
new Thread(() -> {
for (String str : list) {
System.out.println("ThreadA : " + str);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("e");
System.out.println("main : add()");
}
}
実行結果
ThreadA : A
main : add()
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 cp8.no10.Main.lambda$0(Main.java:14)
at java.base/java.lang.Thread.run(Thread.java:834)
ConpyOnWriteArrayListクラスの利用
package cp8.no11;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
new Thread(() -> {
for (String str : list) {
System.out.println("ThreadA : " + str);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("e");
System.out.println("main : add()");
}
}
実行結果
ThreadA : A
main : add()
ThreadA : B
ThreadA : C
※CopyOnWriteArrayListではイテレータを作成した時点の状態を参照するので、コンソール上には、追加したオブジェクトはリストに含まれていない。
ふりかえり
- コレクションをマルチスレッド環境下で使う時には要素を追加したり削除する際には注意が必要
- Mapを使いたいときはConcurrentMapインターフェイス
- ArrayListを使いたいときはCopyOnWriteArrayList
- Setを使いたいときはCopyOnWriteArraySet