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();
이 코드에서 발생하는 문제:
- 구조적 손상: 내부 버킷 배열 구조가 깨질 수 있음
- 데이터 손실: 한 스레드의 put이 다른 스레드의 put을 덮어쓸 수 있음
- 무한 루프: HashMap의 내부 연결 리스트가 순환 참조되어 무한 루프에 빠질 수 있음
- 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부터는 더 세분화된 방식 사용:
- 버킷 단위 동기화: 각 해시 버킷에 대해 개별적으로 synchronized 블록 적용
- CAS(Compare-And-Swap) 연산: 원자적 업데이트를 위해 사용
- volatile 변수: -> 스레드간에 메모리 값을 바로바로 공유하도록 강제하는 키워드!!
자자 그럼 요약
.
특히 backet vs segment 용어가 헷갈림.
Bucket | 우편함 칸 | 데이터 저장 자리 |
Segment | 우편함 구역 | 락 효율적으로 걸기 위해 나눈 구역 (옛날 방식) |
'TIL' 카테고리의 다른 글
관계형DB vs NoSQL (0) | 2025.04.21 |
---|---|
TPS , Latency, 에러율, CPU 사용률이 높을때 (0) | 2025.04.17 |
AI_ 음성 분할에 대해서 (0) | 2025.04.14 |
flutter 카카오 앱에서 로그인 이슈 삽질기 (0) | 2025.04.11 |
table full scan이 더 좋을수도 있다. (1) | 2025.04.08 |