Collections.synchronizedMap vs ConcurrentHashMap

Jeonghwa·2023년 1월 17일
0

HashTable과 HashMap

위의 개념을 다루기 이전에, HashTable과 HashMap이 있다.
HashTable : JDK1.0부터 키-값(Key-Value)타입의 자료구조로 HashTable을 제공한다.
HashMap : Java2부터 Java Collections Framework에서 제공하는 키-값(Key-Value)타입의 자료구조이다.

  • 공통점 : 둘다 Map인터페이스를 구현하고있기 때문에 제공하는 기능은 같다. 키에 대한 해시값을 사용하여 값을 저장하고 조회하며, 키-값 쌍의 개수에 따라 동적으로 크기가 증가하는 associate array라고 한다.
  • 차이점 : HashMap은 보조 해시 함수(Additional Hash Function)를 사용하기 때문에 보조 해시 함수를 사용하지 않는 HashTable에 비해 해시충돌(hash collision)이 덜 발생할 수 있어 성능이 더 좋다. 또한 HashTable은 구현에 거의 변화가 없는 반면, HashMap은 지속적으로 개선되고 있다.


참고 : d2-Java HashMap은 어떻게 동작하는가?


하지만 이 둘의 가장 큰 차이는 바로 동기화 지원의 유뮤이다.

HashTable의 경우 데이터변경 메서드가 모두 synchronized로 선언이 되어있다.

  • 이는 메서드 호출 전 쓰레드간 동기화 락(Lock)을 걸기 때문에 멀티 쓰레드(Multi-Thread)환경에서도 데이터 무결성을 보장한다.

반면 HashMap은 동기화를 지원하지 않는다.

  • 즉 멀티 쓰레드 환경에서 동시에 접근하게되면 데이터 무결성이 손상될 수 있다.

데이터 무결성 : 데이터의 정확성, 일관성, 유효성이 유지되는 것을 말한다.

따라서 HashMap을 사용할 때는 데이터 무결성을 우려할 필요가 없는 단일 쓰레드에서 사용해야한다. 그렇다면 멀티 쓰레드 환경에서는 성능이 좋지 않은 HashTable을 사용해야할까? 여기서 등장하는 개념이 SynchronizedMapConcurrentHashMap이다.

참고 : SynchronizedMap과 ConcurrentHashMap


SynchronizedMap과 ConcurrentHashMap

SynchronizedMap
Collections 클래스는 랩핑(Wrapping)된 컬렉션을 반환하는 다형성 알고리즘을 제공한다. 해당 synchronizedMap()메서드는 매개변수에 Map을 넣어주면 스레드로 부터 안전한 동기화 Map을 반환한다.

  • SynchronizedMap은 객체 수준 잠금(Object level lock)을 사용하여 동기화하기 때문에, put 또는 get과 같은 작업을 수행하려면 먼저 lock을 획득해야한다.
  • 이처럼 전체 컬렉션을 잠그는 것은 오버헤드이며, 한 스레드가 lock을 유지하는 동안(가지고 있는 동안) 다른 스레드는 컬렉션을 사용할 수 없다.

사용법

Map<Integer, String> map = new HashMap<>();
map.put(1,"hi");
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(map);

ConcurrentHashMap
검색 및 업데이트에 대한 높은 동시성을 지원하는 HashMap의 향상된 기능으로 JDK 1.5에 도입되었다. 이는 스레드로 부터 안전하며, Multi Thread환경에서 복잡한 문제 없이 단일 객체로 작동할 수 있다.

  • 객체 수준 잠금(object level lock)이 아닌 좀 더 세분화된 해시맵 버킷 수준 잠금(hashmap bucket level)을 사용한다.
  • 위와 같은 하위 수준 잠금의 효과는 동기화된 컬렉션을 동시에 읽고 쓸 수 있다는 점이며, 이는 훨씬 더 많은 확장성을 가진다.
  • 기본적으로 16개의 버킷이 있고, 이론상 16개의 버킷을 각각 스레드가 바라보고 있다면 16개의 스레드가 버킷을 잠근 후 사용할 수 있다.
    /**
    * The default concurrency level for this table. Unused but
    * defined for compatibility with previous versions of this class.
    */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

이와 같은 방법을 Lock Striping 이라고 한다.

사용법

ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap();
map.put(1,"hi");

null Support

SynchronizedMap과 ConcurrentHashMap은 null key와 value를 다르게 처리한다.

ConcurrentHashMap의 경우는 key와 value에 null을 허용하지 않는다.

ConcurrentHashMap<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();
concurrentHashMap.put(null, null);
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011)
	at java.base/java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
	at Test.main(Test.java:17)

HashMap 또는 LinkedHashMap에 의해 반환된 SynchronizedMap은 하나의 null key와 여러개의 null value를 가질 수 있다.

반면 예외적으로 TreeMap에 의해 반환된 SynchronizedMap은 null value는 가질 수 있지만, null key는 가질 수 없다.

Map<Integer, String> map1 = new HashMap<>();
Map<Integer, String> map2 = new LinkedHashMap<>();
Map<Integer, String> map3 = new TreeMap<>();

Map<Integer, String> synchronizedMap = Collections.synchronizedMap(map1);
Map<Integer, String> synchronizedMap2 = Collections.synchronizedMap(map2);
Map<Integer, String> synchronizedMap3 = Collections.synchronizedMap(map3);

synchronizedMap.put(null,null);
synchronizedMap2.put(null,null);
synchronizedMap3.put(1,null);

참고 :
stackoverflow-ConcurrentHashMap vs Synchronized HashMap
baeldung-Collections.synchronizedMap vs ConcurrentHashMap

profile
backend-developer🔥

0개의 댓글