Spring 강의를 듣다가 HashMap을 사용하면 동시성 문제가 생겨 ConcurrentHashMap을 사용하기를 권장해서 차이점을 찾아보았다.
import java.util.HasMap;
HashMap<K,V> variable name = new HashMap<K,V>();
Type Parameters:
K - the type of keys maintained by this map
V - the type of mapped values
package Study;
import java.util.HashMap;
public class HashMapTest1 {
public static void main(String[] args) {
// HashMap 생성
HashMap<String, Integer> hashMap = new HashMap<>();
// 키-값 추가
hashMap.put("One", 1);
hashMap.put("Two", 2);
hashMap.put("Three", 3);
// 값 가져오기
System.out.println("Value for key 'One': " + hashMap.get("One"));
System.out.println("Value for key 'Two': " + hashMap.get("Two"));
System.out.println("Value for key 'Three': " + hashMap.get("Three"));
}
}

하지만, 다음의 코드처럼 같은 키에 대해 값을 추가하는 경우는 다르다. HashMap은 멀티스레드 환경에서 동시 수정에 대한 동기화가 이루어지지 않기 때문에 여러 스레드에서 동시에 수정되면, 예측 불가능한 결과가 발생한다.
package Study;
import java.util.HashMap;
public class HashMapTest2 {
public static void main(String[] args) throws InterruptedException {
// 공유되는 HashMap 객체
HashMap<String, Integer> sharedHashMap = new HashMap<>();
// 첫 번째 스레드: 키 "A"에 대한 값을 1로 설정
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
sharedHashMap.put("A", 1);
}
});
// 두 번째 스레드: 키 "A"에 대한 값을 2로 설정
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
sharedHashMap.put("A", 2);
}
});
// 두 스레드 시작
thread1.start();
thread2.start();
// 메인 스레드는 두 스레드가 종료될 때까지 대기
thread1.join();
thread2.join();
// 최종 결과 출력
System.out.println("Final value for key 'A': " + sharedHashMap.get("A"));
}
}

synchronizedMap
HashMap의 Collections.synchronizedMap(HashMap)은 모든 메서드에 대한 동기화를 제공하기 때문에 하나의 스레드가 맵을 수정하고 있을 때, 다른 스레드는 대기하게 된다.
그래서 동시성 문제를 해결할 수 있으나, 전체 맵에 대한 락(lock)을 사용하므로 성능이 영향을 받을 수 있다.
이러한 HashMap의 안전한 버전으로 ConcurrentHashMap을 사용할 수 있다. 그리고 key와 value에 null값을 허용하지 않는다.
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
Map<K,V> concurrentHashMap = new ConcurrentHashMap<>();
ConcurrentHashMap는 내부적으로 세그먼트를 사용하여 동기화를 제공하기 때문에 부분적인 동기화를 통해 성능을 향상시킬 수 있다. 즉, 여러 스레드가 동시에 맵을 수정할 수 있다.
package Study;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentHashMapTest {
public static void main(String[] args) throws InterruptedException {
// ConcurrentHashMap 사용
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 첫 번째 스레드: 키 "A"에 대한 값을 1로 설정
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
concurrentHashMap.put("A", 1);
}
});
// 두 번째 스레드: 키 "A"에 대한 값을 2로 설정
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
concurrentHashMap.put("A", 2);
}
});
// 두 스레드 시작
thread1.start();
thread2.start();
// 메인 스레드는 두 스레드가 종료될 때까지 대기
thread1.join();
thread2.join();
// 최종 결과 출력
System.out.println("Final value for key 'A': " + concurrentHashMap.get("A"));
}
}
최종 결과 다음과 같이 같은 결과를 얻을 수 있다.

| HashMap | ConcurrentHashMap | |
|---|---|---|
| key와 value에 null 허용 | O | X |
| 동기화 보장(Thread-safe) | X | O |
| 추천 환경 | 싱글 스레드 | 멀티 스레드 |
참고
Class ConcurrentHashMap<K,V>
Class HashMap<K,V>
ConcurrentHashMap vs Synchronized HashMap
HashMap, HashTable, ConcurrentHashMap 내부 코드