TreeMap의 멀티스레드 환경에서의 문제와 해결 방법
회사 프로젝트에서 멀티스레드 환경에서 TreeMap을 이용하여 기본값을 설정하고, 요청이 오면 해당 값을 확인한 후 TreeMap에 값이 없으면 해당 값을 넣고 가져오는 방식의 로직을 사용했습니다. 그러나 첫 번째 요청에서 새로운 값(예: 1)을 넣고, 두 번째 요청에서 첫 번째 요청에서 넣었던 값을 가져오려고 할 때 NullPointerException(NPE)이 발생하는 이슈가 있었습니다. 이 문제를 재현하고 해결 방법을 찾기 위해 테스트 코드를 작성해보았습니다.
테스트 코드(출처: chatGPT)
void test_3() {
ExecutorService executorService = Executors.newFixedThreadPool(20);
// Runnable to put a value
Runnable put = () -> {
for (int i = 0; i < 100; i++) {
log.error("put: image/jpeg: {}, JPEG: {}", i, i);
mapByMimeType.put("image/jpeg" + i, "JPEG" + i);
}
};
// Runnable to get a value
Runnable get = () -> {
for (int i = 0; i < 100; i++) {
String value = mapByMimeType.get("image/jpeg" + i);
log.error("get: image/jpeg: {}, {}", i, value);
}
};
// Submitting 10 put tasks
for (int i = 0; i < 10; i++) {
executorService.submit(put);
}
// Submitting 10 get tasks
for (int i = 0; i < 10; i++) {
executorService.submit(get);
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
log.error("Exception: {}", e.getMessage());
}
System.out.println("Finished without exception");
}

테스트 결과
특정 번호의 작업 로그를 확인해보았습니다:
• 875번줄: 55번 작업이 먼저 get을 동작했는데 아직 put하지 않았기 때문에 null이 뜨는 것은 정상입니다.
• 986번줄: 55번 값을 TreeMap에 넣었습니다 (put 작업 완료).
• 1027번줄: 위에서 값을 넣었음에도 null이 뜨는 현상이 발생했습니다.
원인 분석
TreeMap은 레드-블랙 트리(Red-Black Tree)를 기반으로 하고 있으며, 삽입 또는 삭제 시 트리의 균형을 유지하기 위해 내부적으로 회전 연산(rotate)을 수행합니다. 이 회전 연산 중에 다른 스레드가 get 연산을 수행하면, 회전 중간 상태에서 null 값을 반환하거나 NullPointerException이 발생할 수 있습니다.
정리하자면 TreeMap은 멀티스레드 환경에서 부적합한 Map 컬렉션입니다.
해결 방법
멀티스레드 환경에서 안전하게 Map을 관리하려면 다음과 같은 방법을 사용할 수 있습니다:
- 메서드를 synchronized로 선언:
• 각 메서드에 동기화 블록을 추가하여 동시 접근을 제어합니다.
-
Collections.synchronizedSortedMap 사용
-
ConcurrentSkipListMap 사용
결론
TreeMap은 멀티스레드 환경에서 안전하지 않기 때문에, 동기화된 컬렉션을 사용하거나 스레드 안전한 컬렉션인 ConcurrentSkipListMap을 사용하여 멀티스레드 환경에서도 안전하게 엔티티를 관리할 수 있습니다.
위의 테스트 결과와 해결 방법을 통해 멀티스레드 환경에서 Map을 안전하게 관리하는 방법을 고려해야 합니다.
추후 위의 컬렉션들에 대해 확인해보자.