본문 바로가기
Language/Java

[JAVA] 멀티스레드 환경의 구원자 : ConcurrentHashMap을 써야 하는 진짜 이유

by Papa Martino V 2026. 1. 19.
728x90

ConcurrentHashMap
ConcurrentHashMap

동시성 제어의 효율성을 극대화하는 자바의 마법, ConcurrentHashMap 분석

1. 왜 다시 ConcurrentHashMap인가?

자바 개발자라면 누구나 한 번쯤 HashMap의 편리함에 매료됩니다. 하지만 서비스의 규모가 커지고 멀티스레드 환경(Multi-threaded Environment)이 구축되는 순간, HashMap은 예측 불가능한 에러를 뿜어내는 시한폭탄으로 변하곤 합니다. 그렇다고 모든 메서드에 락(Lock)을 거는 Hashtable이나 Collections.synchronizedMap을 쓰자니 성능이 발목을 잡습니다. 오늘 우리는 '안전성''성능'이라는 두 마리 토끼를 완벽하게 잡은 ConcurrentHashMap의 탄생 배경과 그 독특한 동작 원리를 깊이 있게 파헤쳐 보겠습니다.

2. 기존 방식의 한계와 고질적인 문제점

단순한 HashMap은 스레드 안전(Thread-safe)하지 않습니다. 두 개 이상의 스레드가 동시에 put()을 시도할 경우, 내부 데이터가 손실되거나 무한 루프에 빠지는 심각한 결함이 발생할 수 있습니다. 이를 해결하기 위해 과거에는 다음과 같은 방식을 사용했습니다.

  • Hashtable: 메서드 전체에 synchronized 키워드를 사용합니다. 한 스레드가 작업 중이면 다른 모든 스레드는 대기해야 하므로 심각한 병목 현상이 발생합니다.
  • SynchronizedMap: 객체 전체에 락을 걸어 안전성을 확보하지만, Hashtable과 마찬가지로 성능 저하를 피할 수 없습니다.

3. ConcurrentHashMap의 핵심 무기: 분할 잠금(Lock Striping)

ConcurrentHashMap이 혁신적인 이유는 '락 분할(Lock Striping)' 기술에 있습니다. 맵 전체를 하나의 큰 락으로 묶는 대신, 데이터를 여러 개의 세그먼트(Segment)로 나누어 관리합니다. 즉, 1번 버킷에 접근하는 스레드와 10번 버킷에 접근하는 스레드가 서로를 방해하지 않고 동시에 작업을 수행할 수 있는 구조입니다. 자바 8 이후부터는 내부적으로 더욱 진화하여 Segment 방식 대신 CAS(Compare-And-Swap) 연산과 volatile 키워드, 그리고 필요한 노드에만 부분적으로 락을 거는 방식을 채택하여 동시성 성능을 극한으로 끌어올렸습니다.

4. 자료구조별 스레드 안전성 및 성능 비교

개발자가 상황에 맞는 최적의 컬렉션을 선택할 수 있도록 주요 맵(Map) 인터페이스 구현체들을 비교해 보았습니다.

구분 HashMap Hashtable ConcurrentHashMap
스레드 안전성 불가능 가능 (전체 락) 가능 (부분 락/CAS)
성능 (멀티스레드) 안전하지 않음 매우 낮음 매우 높음
Null 허용 여부 Key, Value 허용 모두 불가 모두 불가
주요 특징 단일 스레드 최적화 과거 레거시 코드 현대적 동시성 환경 표준

5. 실무에서 ConcurrentHashMap을 선택해야 하는 결정적 순간

  1. 읽기 작업이 쓰기 작업보다 많을 때: ConcurrentHashMap은 읽기 작업(get)에 대해서는 락을 걸지 않습니다. 수많은 스레드가 동시에 조회하더라도 성능 저하가 거의 없습니다.
  2. 실시간 로그 처리 및 카운팅: 분산된 환경에서 공통 리소스를 카운팅하거나 캐싱할 때 데이터 정합성을 보장하며 최고의 성능을 냅니다.
  3. 복합 연산의 필요성: putIfAbsent(), computeIfPresent()와 같은 원자적(Atomic) 연산을 기본으로 제공하여 복잡한 로직에서도 스레드 안전성을 쉽게 확보할 수 있습니다.

6. 전문가의 조언: 주의할 점

ConcurrentHashMap이 만능은 아닙니다. size() 메서드나 isEmpty()와 같은 전체 상태 확인 작업은 실시간으로 변하는 데이터를 집계하므로 아주 정밀한 순간의 값을 보장하지 않을 수 있습니다. 또한, Null 값을 허용하지 않는다는 점을 명심하여 NullPointerException을 방지해야 합니다.

내용 출처 및 참고 문헌

  • Oracle Java SE Documentation: java.util.concurrent.ConcurrentHashMap
  • Brian Goetz, "Java Concurrency in Practice" (Addison-Wesley Professional)
  • Joshua Bloch, "Effective Java 3rd Edition" (Pearson Education)
728x90