- 문제가 발생한 코드 -
if(user.getSkillSet().size() > 0) {
user.getSkillSet().forEach(
(skillCode, skill)
-> {
LocalDateTime ts = skill.getTimeStamp();
LocalDateTime now = LocalDateTime.now();
if (ts.plusSeconds(60).compareTo(now) >= 0) { //지속시간이 남은 경우
skill.effectUser(user);
} else { //지속시간이 지난 경우
// user.removeSkill(skillCode);
}
});
}
- 의미 알아보기 -
"ConcurrentModificationException"
무슨 뜻이지?
Concurrent
동시에 일어나는, 서로 부딪히는
Modification
수정하는
Exception
예외
즉, 동시에 수정이 일어나서 서로 부딪힐 때 발생하는 예외
- 예외가 발생하는 이유 -
Map<String, String> hm = new HashMap<>();
hm.forEach(
(param1, param2) -> {
//do something
});
hm이라는 맵이 있다.
이 맵으로부터 요소를 하나씩 꺼내와서 forEach 반복문을 처리한다.
그런데 처리 과정 중에 hm이라는 맵 자체가 수정되면 어떻게 될까?
예를 들어서, 인덱스를 3까지 돌았는데,
갑자기 요소 하나가 삭제되면서 인덱스가 한 칸씩 당겨졌다면?
맵을 가지고 forEach를 돌리고 있는데, 그 맵 그 자체를 수정해버리니까
서로 충돌이 일어나는 것이다.
map.forEach() 해서 돌리고 있는 반복문 안에서 map.remove() 를 썼고,
map 자체를 수정해 버리니까 문제가 생겨 버렸다.
예를 들어, map.forEach() 반복문을 돌리려면 index 가 있어야 하는데,
map 안에 size()가 1인 상태에서 요소를 remove() 해버리면
size()가 0이 되면서 index 자체가 사라져버린다.
결과적으로 forEach() 문을 돌리고 있는 도중에 index 가 사라져버리는,
자기 부정을 해버리는 상황이 된다.
이러한 상황에서 발생하는 예외가 ConcurrentModificationException이다.
- 해결책1 -
얼른 생각났던 해결책은 remove()를 나중에 부르는 방법이다.
map.forEach() 도는 동안에만 remove()를 안 하면 된다.
일단 반복문 안에서는 remove()를 당장 하지 않고 대상을 기억만 해 두었다가,
반복문이 끝나고 나면 하나씩 remove()하면 되지 않을까?
에를 들어서 새로운 변수 하나를 만들어서
삭제하고 싶은 대상의 index만 따로 모아 둔다.
그리고 map.forEach() 반복문이 끝나고 나면
아까 삭제할 index만 모아 두었던 변수를 가지고
다시 반복문을 돌리면서 remove()를 실행하면 된다.
근데, 이거는 아쉬운 점이 있다.
삭제하고 싶은 index를 고르는 반복문 한 번,
삭제하는 반복문 한 번,
총 일을 두 번하는 꼴이다.
괜히 불필요한 코드들도 생기고, 복잡해질 것이다.
- 해결책2 -
그래서 보통 많이 쓰는 방법이 있다.
map을 바탕으로 iterator를 만들어서 while() 문을 돌린다.
예를 들어 아래와 같다.
Iterator<Integer> iter = user.getSkillSet().keySet().iterator();
while (iter.hasNext()) {
Integer skillCode = iter.next();
//do something
if (skillCode == 3) {
iter.remove();
}
}
일단 다음 index를 새로운 변수에 저장해 둔다
그리고 처리가 끝나고 삭제를 한다.
- 주의 -
삭제할 때는 iterator를 통해서 remove()해야 한다.
위에서 iter.remove() 줄이 이에 해당한다.
iterator를 만들 때 재료로 사용했던 map을 가져와서
map.remove(key);
이렇게 해버리면 concurrentModificationException이 그대로 발생함.
iterator 객체는 전달 받은 사항이 없기 때문이다.
iter.remove()하면 알아서 그 뿌리의 map 을 찾아다가 지금 돌고 있는 로테이션의 요소만 삭제해 준다.
iterator룰 만들 때 이미 그 map의 정보까지 들고 있던 것이다.
참고자료 : https://aljjabaegi.tistory.com/533 / https://jy2694.tistory.com/16
'Java' 카테고리의 다른 글
[JAVA] switch 문을 반복문으로 바꾸기 (0) | 2022.03.19 |
---|---|
[JAVA] isNumber / isNumeric 문자열(String)이 숫자인지 확인하는 함수 (0) | 2022.03.19 |
[JAVA] 추상 클래스 인터페이스 차이점 / abstract class interface 사용법 (0) | 2022.03.19 |
mybatis / servelet / jsp 등 개념 (0) | 2022.03.17 |
jdk / jre / jvm / lts 무슨 말이지? (0) | 2022.03.10 |