Closed uhm0311 closed 1 month ago
Collections.synchronizedMap 대신에 ConcurrentHashMap 사용하면, ConcurrentModificationException 발생을 막을 수 있나요?
@jhpark816 현민님 대신 답변드리자면, 말씀하신대로 동작합니다.
@oliviarla @brido4125 TO-BE 자료구조로 변경하는 것이 좋은 지를 검토 바랍니다.
@jhpark816
제 생각엔 missedKeyList, missedKeyMap, trimmedKeyMap 등의 자료구조를 가져오기 전에 해당 연산의 getter 내에서 Future.get()을 호출하여서 명시적으로 연산이 완료된 후에 가져올 수 있음을 사용자에게 알려주는게 좋을 것 같습니다.
위와 같은 방법 사용 시, 별도의 동기화 자료구조를 사용하지 않아도 됩니다.
ConcurrentModificationException 발생하도록 두어도 될 것 같습니다. 의견으로 참고 바랍니다.
future.get()
호출 이후에 missed or trimmed keys 조회한다면 해당 예외가 발생하지 않습니다.future.get()
호출 없이 missed or trimmed keys 조회할 경우에는 예외가 발생할 수 있습니다.
이러한 예외가 발생하면 응용에 치명적일 수 있나요?스프링 프레임워크 기반의 WAS 환경에서 ConcurrentModificationException이 발생했을 때 이에 대한 예외 처리를 해주지 않으면 사용자가 에러 응답을 받습니다. 따라서 ConcurrentModificationException이 발생할 수 있음을 응용 개발자가 사전에 인지하고 이에 대한 처리를 해주어야 합니다.
ConcurrentModificationException 발생하도록 둔다면 Collections.synchronized...()
을 사용하지 않아도 되고, 따라서 일반적인 ArrayList와 HashMap을 사용하면 됩니다.
저도 ConcurrentModificationException을 막기 위해 future.get() 호출 등의 방법으로 연산이 모두 완료된 후에만 missed, trimmed keys를 조회할 수 있도록 하는 것이 좋은 것 같습니다.
@uhm0311
우선은 동기화 자료구조를 사용하면 좋겠습니다.
아래와 같이 수정할 수 있는 지 한번 검토해 주세요. missedKeyMap이 있어서 missedKeyList 항목은 제거할 수 있지 않나 생각됩니다.
public SMGetResult(int totalResultElementCount, boolean reverse) {
this.totalResultElementCount = totalResultElementCount;
this.reverse = reverse;
// this.missedKeyList = new CopyOnWriteArrayList<>();
this.missedKeyMap = new ConcurrentHashMap<>();
this.trimmedKeyMap = new ConcurrentHashMap<>();
this.mergedTrimmedKeys = new ArrayList<>();
this.mergedResult = new ArrayList<>(totalResultElementCount);
}
@jhpark816
missedKeyList 필드를 제거하고 Getter를 아래와 같이 수정할 수 있습니다.
public List<String> getMissedKeyList() {
return new ArrayList<>(missedKeyMap.keySet());
}
@uhm0311 동기화 자료 구조를 사용하면서, missedKeyList 필드를 제거하시죠.
위의 PR로 본 이슈는 처리가 완료되었으므로, close 합니다.
문제점
FIX: Concurrency problem in locator allNodes. ( https://github.com/naver/arcus-java-client/pull/769/commits/3d216eee6423aa14bed9bd19d1e6e4281300089a )
위 PR을 리뷰하며
Collections.synchronizedList()
사용을 고려했습니다. 그러나 아래와 같은 자료를 찾아,Collections.synchronizedList()
는 사실 동시성 제어를 보장하지 않는다는 것을 알게 되었습니다.https://stackoverflow.com/a/28998561
이것이 사실인지 검증하기 위해 아래와 같은 테스트 코드를 수행했습니다.
결과는 다음과 같이 ConcurrentModificationException이 발생합니다.
다른 부분은 다 동일하고, List 객체를 CopyOnWriteArrayList 객체로 변경했을 때는 다음과 같이 의도된 출력 결과가 나옵니다.
Collections.synchronizedList()
Java Doc에 있는 주석에 의하면, Iteration 시 아래와 같이 사용자가 직접 Lock을 잡아야 합니다.다음과 같이 Iteration 시 Lock을 잡도록 테스트 코드를 수정했을 때는 ConcurrentModificationException이 발생하지 않고 의도된 결과가 출력됩니다.
AS-IS
현재
Collections.synchronized...()
을 사용하는 코드는 SMGetResult 클래스 뿐입니다.TO-BE
아래와 같이 정말 동시성 제어가 제공되는 객체를 사용해야 합니다.