TIL

ConcurrenHashMap 원리

하얀잔디 2025. 4. 15. 20:53

concurrentHashMap은 멀티 스레드 환경에서 잘 동작한다고 그냥 알고있다. 근데 왜?

 

HashMap의 멀티스레드 취약점

일반 HashMap은 멀티스레드 환경에서 심각한 문제를 일으킬 수 있습니다.

 
Map<String, String> map = new HashMap<>();
Runnable task = () -> { 
    for(int i=0; i<1000; i++) {
        map.put("key"+i, "value"+i);
    }
};
new Thread(task).start();
new Thread(task).start();

이 코드에서 발생하는 문제:

  1. 구조적 손상: 내부 버킷 배열 구조가 깨질 수 있음
  2. 데이터 손실: 한 스레드의 put이 다른 스레드의 put을 덮어쓸 수 있음
  3. 무한 루프: HashMap의 내부 연결 리스트가 순환 참조되어 무한 루프에 빠질 수 있음
  4. ConcurrentModificationException: 한 스레드가 순회 중일 때 다른 스레드가 수정하면 발생

ConcurrentHashMap의 해결책: 세분화된 잠금

JDK 7 이전: Segment 기반 접근

 
// 내부적으로 이런 구조였습니다
static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile HashEntry<K,V>[] table;
    // ...
}
  • Map을 16개(기본값)의 Segment로 분할
  • 각 Segment는 ReentrantLock을 상속받아 독립적으로 잠금
  • 동시에 최대 16개 스레드가 다른 세그먼트에 접근 가능

JDK 8 이후: Node 단위 잠금과 CAS

 
java
// 간소화된 내부 구조
transient volatile Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;  // 값이 volatile로 선언됨
    volatile Node<K,V> next;
    // ...
}

JDK 8부터는 더 세분화된 방식 사용:

  1. 버킷 단위 동기화: 각 해시 버킷에 대해 개별적으로 synchronized 블록 적용
  2. CAS(Compare-And-Swap) 연산: 원자적 업데이트를 위해 사용
  3. volatile 변수: -> 스레드간에 메모리 값을 바로바로 공유하도록 강제하는 키워드!!

 

 

자자 그럼 요약 

 

 

특히 backet vs segment 용어가 헷갈림.

Bucket 우편함 칸 데이터 저장 자리
Segment 우편함 구역 락 효율적으로 걸기 위해 나눈 구역 (옛날 방식)