서론
여러 강의나 예제를 보면 동시성을 생각하면 concurrentHashMap을 사용하라고 한다. 왜일까? 그리고 도대체 뭘까?
ConcurrentHashMap 이란?!
- Java의 java.util.concurrent 패키지에 포함된 동시성 map
- 여러 스레드가 동시에 데이터를 안전하게 읽고 쓸 수 있도록 설계된 자료구조
- 기본적으로 동시성 제어를 위해 여러 메커니즘을 사용하여 성능 저하 없이 스레드 안전성을 제공
ConcurrentHashMap의 특징과 동작 방식
1. 세분화된 잠금
- ConcurrentHashMap은 내부적으로 세그먼트(segment)나 버킷(bucket) 수준에서 잠금을 관리. 이는 전체 맵이 아닌 일부에만 잠금을 걸어 여러 스레드가 병렬로 작업할 수 있게 함
- Java8 이후로는 세그먼트 대신 개별 버킷 수준에서 동시성을 제어하는 방식으로 개선됨
2. 읽기 작업의 비동기 처리
- 대부분의 읽기 작업은 잠금을 사용하지 않거나 최소한의 잠금만 사용하여 처리됨. 이는 읽기 작업의 성능을 극대화 함
3. 쓰기를 위한 잠금
- 쓰기 작업은 잠금을 사용하여 데이터 일관성을 유지함. 전체 맵을 잠그지 않고, 특정 버킷이나 노드에 대해서만 잠금을 걸기 때문에 병목 현상을 최소화함
4. 리사이징
- 해시 테이블의 크기가 증가해야할 때 리사이징 작업은 병렬로 수행됨. 이는 전체 맵을 잠그지 않고도 안전하게 크기를 조정할 수 있게 함
ConcurrentHashMap VS HashMap VS synchronizedMap 비교
HashMap
- 비동기적으로 동작하며 멀티스레드 환경에서 안전하지 않음 (동기화 지원x)
- 스레드가 동시에 접근할 경우 데이터의 일관성이 깨질 수 있음
Collections.synchronizedMap
- 내부적으로 모든 접근에 대해 동기화 블록을 사용
- 동시성 제어는 가능하지만, 모든 쓰기 작업과 대부분의 읽기 작업에서 전체 맵 잠금이 발생하여 성능 저하가 심함
ConcurrentHashMap
- 전체 맵 잠금이 아닌 세분화된 잠금을 사용하여 더 많은 스레드가 병렬로 작업할 수 있음
- 읽기 작업은 대부분 잠금을 사용하지 않거나 최소한의 잠금을 사용하여 높은 효율성 제공
HashMap과 ConcurrentHashMap 코드 예제
* HashMap 예제 코드 (동시성 문제로 데이터의 일관성 깨짐)
public class HashMapExample {
private static Map<String, Integer> map = new HashMap<>(); // HashMap 선언
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10); // 10개의 고정된 스레드를 가진 스레드풀 생성
for (int i = 0; i < 1000; i++) { // 1000번 반복하면서 map.put() 작업을 실행
int finalI = i;
executorService.execute(() -> map.put("key" + finalI, finalI));
}
executorService.shutdown(); // 더 이상 새로운 작업을 받지 않게 함
executorService.awaitTermination(1, TimeUnit.MINUTES); // 모든 작업이 완료되거나, 1분이 지나면 종료
// 예상되는 출력은 1000이지만, 실제로는 일관되지 않은 결과를 출력할 수 있음
System.out.println("Map size: " + map.size()); // 실행 결과가 매번 달라짐 996, 975, 992 ...
}
}
* ConcurrentHashMap 예제 코드 (동시성 문제 해결)
public class ConcurrentHashMapExample {
private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); // ConcurrentHashMap 선언
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10); // 10개의 고정된 스레드를 가진 스레드풀 생성
for (int i = 0; i < 1000; i++) { // 1000번 반복하면서 map.put() 작업을 실행
int finalI = i;
executorService.execute(() -> map.put("key" + finalI, finalI));
}
executorService.shutdown(); // 더 이상 새로운 작업을 받지 않게 함
executorService.awaitTermination(1, TimeUnit.MINUTES); // 모든 작업이 완료되거나, 1분이 지나면 종료
// ConcurrentHashMap을 사용하면 예상대로 1000이 출력.
System.out.println("Map size: " + map.size());
}
}
'BE > Java' 카테고리의 다른 글
[JAVA] HashMap과 HashTable의 차이 (1) | 2024.06.03 |
---|---|
[JAVA] 자바 intersection type이 뭔지 알아보자! (with 람다) (0) | 2023.12.26 |
[JAVA] Generic 한 걸음 나아가기! (2) 실전편 (3) | 2023.12.23 |
[JAVA] Generic 한 걸음 나아가기! (1) 개념편 (1) | 2023.12.23 |