본문 바로가기
Language/JAVA

[자바 멀티스레딩] 자바 동시성 고급: ConcurrentHashMap 사용법

by YoonJong 2025. 4. 1.
728x90

ConcurrentHashMap 은 멀티스레드 환경에서 데이터를 안전하고 효율적으로 관리할 때 자주 사용하는 클래스다.

예로, 웹 서버에서 사용자 요청 수를 집계하거나 캐시 데이터를 저장할 때, 데이터 충돌 없이 빠르게 동작한다.

 

ConcurrentHashMap

- 동시성 환경에서 안전한 해시맵 구현체

- Java8 부터는 CAS(Compare And Swap) 와 세밀한 락으로 동시성 최적화.

- 읽기는 락 없이, 쓰기는 최소 범위만 락 걸림

- 기본 초기 용량값은 16 

 

사용목적

- 멀티스레드 환경에서 데이터 일관성 유지 및 성능 향상

 

주요메서드

- put(K key, V value): 키-값 쌍 추가 또는 갱신

- get(Object key): 키로 값 조회

- remove(Object key): 키-값 쌍 삭제

- computeIfAbsent(K key, Function<K, V> mappingFunction): 키 없으면 값 계산 후 추가

- forEach(BiConsumer<K, V> action): 모든 항목 순회

- size(): 현재 크기 반환 (정확성 보장 안 됨)

 

HashMap 과의 차이 

- HashMap: 단일 스레드용, 동기화 없음

- Collections.synchronizedMap: 전체 락으로 동기화, 성능 저하

- ConcurrentHashMap: 세그먼트 락/CAS로 동시성 최적화, 읽기 락 없음

 

장점

- 높은 동시성 : 읽기 작업은 락 없이 처리, 쓰기 충돌 최소화

- 성능 : 여러 스레드가 동시에 작업 시 synchronized보다 빠름

- 안전성 : 데이터 무결성 보장

- 확장성 : 스레드 수 증가해도 성능 저하가 적음

 

단점

- 복잡성 : 내부 동작 이해가 필요하다. 단순 작업에는 과도한 작업을 수 있다.

- 메모리 : 세그먼트 구조로 일반 HashMap보다 사용량이 많음

- 정확성 제한 : size() , isEmpty() 는 순간값, 완전 정확성이 보장되지 않음.

- 제한 : key / value 에 Null 허용 안 함

 

public class ConcurrentMapBasic {

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

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                map.put("key" + i, i);
                System.out.println(Thread.currentThread().getName() + " 추가: key" + i);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 100; i < 200; i++) {
                map.put("key" + i, i);
                System.out.println(Thread.currentThread().getName() + " 추가: key" + i);
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("최종 크기: " + map.size());
        System.out.println("key10 값: " + map.get("key10"));
    }

}

//출력값
Thread-0 추가: key0
Thread-0 추가: key1
Thread-0 추가: key2
Thread-0 추가: key3
...
...
...
Thread-1 추가: key197
Thread-1 추가: key198
Thread-1 추가: key199
최종 크기: 200
key10 값: 10

 

두 스레드가 충돌 없이 데이터를 추가한다.

- 최종크기 200 / 데이터 무결성 유지

 

--

 

일반 HashMap 으로 변경했을 경우

- 일반 HashMap 은 동기화가 없어서 경쟁 상태(race condition) 이 발생. 

- 동일 키에 대해 두 스레드가 동시에 쓰면 값이 덮어씌어지거나 누락

private static final HashMap map = new HashMap();

//결과값
...
...
...
Thread-0 추가: key99 
최종 크기: 199 (무결성 유지 X)
key10 값: 10

 

728x90

댓글