목차
서론
스터디를 진행하던 도중 Hashtable에 대한 이야기가 조금 나왔었다. 사실 이전까진 Hashtable이라는 것이 있다는 것만 알고 Map의 구현체로 대부분 HashMap을, 아주 가끔 TreeMap을 사용했는데, Hashtable은 HashMap과 비슷하게 Key - Value 쌍의 Map 구현체인 것은 동일하지만 세부적인 내용에서 차이가 있는 구조라고 한다. 이 글에서는 HashMap과 Hashtable의 차이에 대해 조금 다뤄보려고 한다.
HashMap
HashMap은 자바 1.2에서 등장한 Map 인터페이스의 구현체이다. 기본적으로 HashMap은 동기화를 지원하지 않기 때문에 다중 스레드 환경에서는 동시성 이슈가 발생할 수 있다. 이러한 이유로 인해 HashMap에서는 연산 도중 Map이 변경되었을 때를 대비하여 각종 메소드에서 ConcurrentModificationException를 던져서, 오동작을 하는 대신 아예 실패하도록 fail-fast가 구현되어 있다. 하지만 이것이 데이터 무결성을 보장해주진 못하기 때문에 이 예외에 의존하는 프로그램을 작성하면 안 된다고 한다.
하지만 이렇게 비동기적으로 작동한다는 점으로 인해 단일 스레드 환경에서는 HashMap이 훨씬 좋은 성능을 내기 때문에 널리 사용된다고 한다.
별 의미는 없겠지만 HashMap은 Key값에 null을 사용할 수도 있다.
Hashtable
Hashtable은 자바 1.0에서 등장한 Map 인터페이스의 구현체이다. 일단 Map의 구현체이고 Hash를 사용한다는 점에서 HashMap과 유사하지만 가장 큰 차이점은 거의 모든 메소드에 synchronized
가 붙어서 동기적으로 작동한다는 점이다.
이렇게 단순 삽입, 제거, 조회 등을 포함한 거의 모든 메소드에 synchronized
가 붙다보니 뭘 하려고만 하면 스레드 간에 잠금을 걸고 푸는 것을 반복하여 Hashtable은 연산 속도가 느린 편이라고 한다.
또한, HashMap과는 달리 Key값에 null을 사용하려고 하면 NullPointerException을 발생시킨다.
그런데 HashMap은 동기화를 지원하지 않으니 결국 스레드 안정성이 필요한 환경에서는 성능이 매우 떨어진다고는 하지만 Hashtable을 사용해야 하는 것이 아닌가? 하는 의문이 생긴다.
SynchronizedMap
SynchronizedMap은 HashMap을 포함하여 다양한 Map 구현체들에 스레드 안정성을 부여하기 위해 만들어진, Collections 클래스에 내부적으로 구현되어 있는 클래스이다. SynchronizedMap은 아래와 같이 선언하여 사용할 수 있다.
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
여기서 synchronizedMap()
의 파라미터는 HashMap이 아닌 다른 Map 구현체가 와도 상관없다.
하지만 이 방법은 두 가지 문제가 있는데, 첫째로는 감싸지지 않은 원본 Map에 모종의 방법으로 접근하게 되면 스레드 안정성을 보장할 수 없게 된다. SynchronizedMap은 아래와 같이 구성되어 있다.
이를 보면 SynchronizedMap은 파라미터로 받은 Map을 단순 참조로 받아 감싸고, 연산을 수행할 때마다 스스로를 잠그고 참조한 Map의 연산을 수행하는 것을 볼 수 있다. 즉, Map 자체에는 스레드 안정성이 보장되지 않는다! 이로 인해 원본 Map에 변화가 일어나면 그 변화에 영향을 받아 스레드 안정성을 보장할 수 없다. 따라서, 이 방법으로 스레드 안정성을 보장하고싶다면 기존에 선언된 Map 객체를 감싸는 것이 아니라 외부에서 접근할 수 없는 새로운 Map을 생성하여 감싸야 할 것이다.
또한 이 방법은 우선 Map을 선언하고 그것을 다시 감싸야한다는 점에서 자원 낭비가 발생한다. 이러한 문제점들을 해결하기 위해 ConcurrentHashMap를 사용할 수 있다.
ConcurrentHashMap
ConcurrentHashMap은 자바 1.5에서 등장한 ConcurrentMap의 구현체이다. 이 ConcurrentMap 인터페이스는 Map을 상속받아 작성되었기 때문에 크게 보면 Map의 구현체라고도 볼 수 있다.
ConcurrentHashMap은 Hashtable과 달리 동기화가 굳이 필요하지 않은 메소드에는 아예 synchronized
를 사용하지 않고, 동기화가 필요한 메소드에는 무작정 synchronized
를 거는 것이 아니라 꼭 필요한 부분에만 사용하여 Hashtable에 비해 성능이 크게 향상되었다.
결론
Hashtable은 굉장히 오래된 클래스인 만큼 자바를 만든 개발자들도 사용하지 않는 것을 권하고 있다. 스레드 안정성이 필요없는 환경이라면 HashMap을 사용하는 것이 훨씬 이득이고, 스레드 안정성이 필요한 상황이라도 Hashtable 대신 ConcurrentHashMap을 사용하는 것이 좋을 것이다.
'Programming Language > Kotlin & Java' 카테고리의 다른 글
[Kotlin] Java와 Kotlin, 그리고 Lombok (0) | 2023.04.21 |
---|---|
[Kotlin] 불변 컬렉션과 가변 컬렉션 (0) | 2023.04.18 |
[Effective Java] 상속보다는 컴포지션을 사용하라(Feat. Stack) (0) | 2023.02.27 |
[Kotlin] 빌더 패턴과 코틀린, 그리고 Default Parameter와 Named Argument를 이용하여 코틀린에서 빌더 패턴의 효과를 내보기 (0) | 2023.02.13 |
[Java/Kotlin] 자바 Stream을 통해 리스트의 요소를 특정 key 기준으로 grouping하여 다른 객체로 합치기 및 코틀린으로 변경해보기 (0) | 2023.02.09 |
댓글