본문 바로가기

TIPS

#ConcurrentHashMap 안전한 멀티스레드 Map의 모든 것

728x90
반응형
SMALL

 

 

왜 필요할까?

 

  • HashMap은 멀티스레드 환경에서 안전하지 않다.
  • 여러 스레드가 동시에 put, get 하면 데이터 꼬임(corruption), 무한 루프, 심지어 ConcurrentModificationException이 발생할 수 있다.
  • Hashtable은 스레드 안전하지만 전체에 락을 건다. → 성능 저하.

👉 그래서 등장한 게 ConcurrentHashMap이다.

동시성 처리 + 성능 두 마리 토끼를 잡기 위해 내부 구조가 다르게 설계됐다.


⚙️ 내부 구조 핵심: 분할 락(Segment Lock)

ConcurrentHashMap은 내부적으로 버킷(bucket) 을 여러 개로 쪼갠 뒤, 각 버킷별로 락을 걸어 동기화한다.

이를 Segment Lock 구조라고 한다.

  • 하나의 버킷에만 변경이 발생하면 다른 버킷은 락 없이 처리 가능
  • 여러 스레드가 서로 다른 버킷에 동시에 접근해도 성능 저하 없음
  • 자바 8부터는 Segment 개념 대신 bin 구조 + CAS + Synchronized 를 혼합 사용해서 더 빠르게 작동한다.

 


 

기본 사용 예시

 

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 값 넣기
map.put("apple", 1);
map.put("banana", 2);

// 값 가져오기
System.out.println(map.get("apple"));

// 동시 업데이트
map.compute("apple", (key, val) -> (val == null) ? 1 : val + 1);

System.out.println(map.get("apple"));
 

주요 동시성 메서드
메서드
설명
compute
키의 현재 값 기반으로 새 값 계산
computeIfAbsent
키가 없으면 계산하여 추가
computeIfPresent
키가 있으면 계산하여 수정
putIfAbsent
키가 없을 때만 put
forEach
병렬 안전한 반복 처리

 


🔗 동기화 블록 vs ConcurrentHashMap 비교

 
항목
HashMap + synchronized
ConcurrentHashMap
스레드 안전성
O
O
동기화 방식
전체 락
분할 락
동시 처리
낮음
높음
성능
경합 많으면 저하
경합 많아도 유지
  • 셀 병합
  • 행 분할
  • 열 분할
  • 너비 맞춤
  • 삭제

실전에서 언제 쓰냐?
  • 다중 스레드 환경에서 캐시(Map) 관리
  • 요청별 상태 저장 (예: 세션별 데이터)
  • 공유 설정 데이터
  • 쓰기보다 읽기가 많은 경우 효율적

주의할 점!
  • size() 는 정확하지 않을 수 있다 → 동시 수정 중이면 정확 보장 X
  • 대규모 연산은 forEach와 reduce 제공 → Stream API처럼 사용 가능

 

long count = map.mappingCount(); // size()보다 동시성에 안전
 

 


마무리

ConcurrentHashMap은 멀티스레드에서 안전한 Map 구현체 중 가장 자주 쓰인다.

성능과 안정성 모두 중요할 때 필수로 알아야 한다.


 

728x90
반응형
LIST