기초 지식/Java

[Java] HashTable, ConcurrentHashMap에 대해서

MarrRang 2021. 9. 28. 21:25

HashTable vs HashMap

Java를 사용하면서 HashMap은 많이 사용해봤지만 HashTable은 들어만 보고 거의 사용해본 적은 없는 것 같습니다.

하지만 DB나 여러 라이브러리에서 사용하고 있을 것으로 생각이 되어서 정리해보려 합니다.

 

우선은 ConcurrentHashMap을 알아보기 전에 HashMap과의 차이를 알아보겠습니다.

HashTable은 HashMap과 매우 유사합니다. 아래에서 사용법을 알아볼텐데 메서드 명조차 거의 동일합니다.

가장 큰 차이점은 Thread-safe 여부입니다. HashTable 내부에서는 synchronized 키워드를 이용해 Thread-safe하도록 구현되어 있습니다.

 

이외에도 Key값에 Null을 허용하는지 여부와 에러를 발생시킬 수 있는 코드를 수행했을 때 미리 에러를 발생시켜서 불필요한 연산을 막는 Enumeration 기능을 제공하는지 여부가 차이점입니다.

  HashTable HashMap
Thread-safe O X
Key에 Null값 허용 여부 X O
Enumeration 제공 여부 O X
전체적인 성능 낮음 높음

 

HashTable을 사용하는 코드가 적은 이유

HashTable은 굉장히 오래된 구현 클래스입니다. 그리고 현재는 거의 수정이 되고 있지 않습니다. 하지만 HashMap 같은 경우에는 최근까지도 지속적으로 개선되어 왔습니다. Java8도 나온지는 오래되었지만 O(N) 시간에서 O(log N)으로 퍼포먼스적 향상도 있었죠.

 

https://johngrib.github.io/wiki/java8-performance-improvement-for-hashmap/

 

Java 8 HashMap 퍼포먼스 향상

균형 트리를 도입해 O(n) 에서 O(log n)으로 향상됐다

johngrib.github.io

 

HashTable에도 분명히 장점이 있습니다. Multi-Thread 환경에서 사용하기에 적절하고 Enumeration도 제공해줍니다. 하지만 저희에게는 성능이 더 중요합니다. 이것을 개선한 HashMap이 있다면 더욱 HashTable을 사용할 이유가 없어지겠네요.

 

ConcurrentHashMap

ConcurrentHashMap은 기본적으로 HashTable, HashMap의 모든 기능을 수행하고 HashTable처럼 Thread-Safe 하면서도 HashMap의 성능에 버금가는 성능을 보유한 구현 클래스입니다. Java 1.5부터 추가되었고 동기화를 보장해야 하는 상황에서 사용하기에 적절합니다.

 

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html

 

ConcurrentHashMap (Java Platform SE 8 )

A hash table supporting full concurrency of retrievals and high expected concurrency for updates. This class obeys the same functional specification as Hashtable, and includes versions of methods corresponding to each method of Hashtable. However, even tho

docs.oracle.com

 

ConcurrentHashMap vs HashTable

ConcurrentHashMap과 HashTable은 모두 Thread-Safe합니다. 둘의 구현은 어떻게 차이가 나길래 성능상 차이가 발생할까요? 두 클래스의 put 메서드를 비교해보겠습니다.

 

//HashTable
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    ....

    addEntry(hash, key, value, index);
    return null;
}
//ConcurrentHashMap
public V put(K key, V value) {
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        ....
            V oldVal = null;
            //*****
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    ....
                }
            }
            ....
        }
    }
    addCount(1L, binCount);
    return null;
}

 

  • HashTable은 메서드에 synchronized 키워드 적용
  • ConcurrentHashMap은 일부 entry에만 synchronized 적용
  • 즉, put 메서드 실행 시 테이블 전체가 Lock에 걸려있는지 혹은 일부 entry에만 걸려있는지의 차이

이외에도 ConcurrentHashMap은 get 메서드에는 synchronized가 아예 없지만 HashTable은 get 메서드에도 동기화를 진행합니다.

 

정리

  HashTable HashMap ConcurrentHashMap
Thread-Safe 여부 O X O
Synchronized 적용 범위 메서드에 적용 - 일부 Entry에만 적용
상대적인 성능 낮음 높음 높음

 

반응형