[트러블슈팅] ConcurrentModificationException
ConcurrentModificationException
내용
private static void dfs(int node, List<Integer> next, List<List<Integer>> list) {
next.removeIf(item -> item == node);
for (int nextNode : list.get(node)) {
next.add(nextNode);
}
for (int nextNode : next) {
dfs(nextNode, next, list);
}
}
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.dfs(Main.java:29)
at Main.dfs(Main.java:30)
at Main.main(Main.java:18)
dfs탐색에서 list의 요소를 삭제하려고 할 때 발생했다.
ConcurrentModificationException은 동시성 문제에서 발생하는 문제였다. 즉, 서로 다른 쓰레드가 간섭하면서 발생하는 것이다.
List나 Map 등 객체를 순회하면서 요소를 삭제하거나 변경할 때 발생한다.
해결
해결 1: 컬렉션을 복사하여 사용하기
한 컬렉션에 여러 쓰레드가 동시에 수정을 할 때 발생하는 문제이므로 임시 컬렉션을 만들어 복사해서 처리해보았다. 성공적으로 실행됐다.
private static void dfs(int node, List<Integer> next, List<List<Integer>> list) { ArrayList<Integer> tmp = new ArrayList<>(); tmp.addAll(next); tmp.removeIf(item -> item == node); for (int nextNode : list.get(node)) { tmp.add(nextNode); } for (int nextNode : tmp) { dfs(nextNode, tmp, list); } }
해결 2: 스트림으로 새로운 컬렉션 얻기
스트림 API를 이용해 원본 컬렉션에 변경을 가하지 않고 새로운 컬렉션을 얻을 수 있다.
private static void dfs(int node, List<Integer> next, List<List<Integer>> list) { next = next.stream() .filter(item -> item != node) .collect(Collectors.toList()); for (int nextNode : list.get(node)) { next.add(nextNode); } for (int nextNode : next) { dfs(nextNode, next, list); } }
해결 3: CopyOnWriteArrayList사용하기
자바 1.5부터 안전한 쓰레드 처리를 위해 나온 라이브러리이다. 내부를 변경하는 작업에 항상 복사본을 만들어서 수행하도록 구현되어 있다.
객체를 매번 복사하지 않고, 전달할 때 해당 상태를 스냅샷으로 가지고 있는 방식이다.
List<Integer> next = new CopyOnWriteArrayList<>();