자료구조 - ConcurrentHashMap
Q. HashMap
을 멀티스레드 환경에서 사용하면 어떤 문제가 생기나요?
HashMap
을 멀티스레드 환경에서 사용하면 어떤 문제가 생기나요?HashMap
은 동기화가 전혀 되어 있지 않아서, 멀티스레드 환경에서 동시에 put이나 resize가 일어나면 내부 구조가 쉽게 깨집니다.
특히 문제가 되는 건 resize 과정입니다.한 스레드가 배열을 확장하고 있는 동안 다른 스레드가 동시에 put을 수행하면 연결 리스트 구조가 잘못 이어질 수 있는데요, 이 경우 무한 루프가 발생하거나, GC가 회수하지 못하는 참조가 남아서 메모리 누수나 CPU 100% 사용 같은 치명적인 문제로 이어질 수 있습니다.
이런 문제를 피하기 위해 멀티스레드 환경에서는 HashMap 대신 ConcurrentHashMap을 사용합니다.
Q. HashMap
과 ConcurrentHashMap
간의 차이를 설명해주세요.
HashMap
과 ConcurrentHashMap
간의 차이를 설명해주세요.HashMap
은 단일 배열을 기반으로 연결 리스트나 트리 구조를 사용해서 데이터를 저장합니다.
하지만 동기화 처리가 전혀 없기 때문에 멀티스레드 환경, 예를 들어 두 스레드가 동시에 put하는 경우 등 에서는 스레드 간 충돌이 발생할 수 있습니다.
반면, ConcurrentHashMap
은 멀티스레드 환경에서도 안전하게 사용할 수 있도록 설계된 Map입니다.
Java 7까지는 내부적으로 Segment
라는 배열을 두고, 각 Segment
마다 락을 걸어 동시 접근을 분산시켰습니다.
하지만 이 구조는 Segment
개수만큼만 병렬처리가 가능하다는 단점이 있어서,
Java 8부터는 Segment
를 제거하고, 각 버킷에 CAS 연산
또는 synchronized 블록
을 적용하는 방식으로 개선됐습니다.
이 덕분에 읽기 연산은 락 없이 빠르게 수행되고, 쓰기 연산도 필요한 버킷에만 락을 걸기 때문에 성능과 안전성 모두 확보할 수 있습니다.
또 하나 중요한 차이는, HashMap
은 null key와 null value를 허용하지만 ConcurrentHashMap
은 둘 다 허용하지 않는다는 점입니다.
Q. ConcurrentHashMap에서는 왜 null key나 null value를 허용하지 않나요?
가장 큰 이유는 명확성과 안정성입니다. map에서 get(key)의 결과가 null일 때, 해당 키가 없어서 null이 반환된 건지, 아니면 실제로 value가 null인 건지 구분이 안되기 때문입니다.
이 모호함은 단일 스레드 환경에서는 containsKey()
로 확인할 수 있지만,
멀티스레드 환경에선 그 사이에 다른 스레드가 값을 바꿔버릴 수 있어서 race condition이 발생할 수 있습니다.
예를 들어, 한 스레드는 put(key, null)을 수행했고, 다른 스레드는 get(key)을 호출했을 때 null이 나왔다면 두 번째 스레드는 "해당 키가 아직 저장되지 않았나?"라고 잘못 판단할 수 있습니다.
그래서 ConcurrentHashMap은 명확성과 안정성 확보를 위해 null 자체를 아예 허용하지 않도록 설계됐습니다.
Race Condition이 무엇인가요?
Race condition은 여러 스레드가 동시에 같은 자원에 접근하면서, 그 순서나 타이밍에 따라 프로그램의 결과가 달라지는 상황을 말합니다. 즉, 의도하지 않은 타이밍 문제로 인해 예측 불가능한 결과나 버그가 발생하는 상태입니다.
예를 들어, 두 개의 스레드가 동시에 같은 변수에 값을 더하려고 할 때, 각 스레드가 읽고 쓰는 순서가 엇갈리면 최종 결과가 정확하지 않을 수 있습니다. 이런 상황이 바로 race condition이고, 주로 공유 자원을 락 없이 사용할 때 발생합니다.
그래서 멀티스레드 환경에서는 synchronized, 락, Atomic 클래스 같은 동기화 기법을 사용해서 race condition을 방지해야 합니다.
Last updated