먼저 HashMap과 HashTable의 차이에 대해서 알아보자.
Key, Value 형식의 데이터를 저장하는 기능을 제공하는 자료 구조로 큰 맥락에서는 동일하다. 그러나 크게 3가지 정도의 차이를 보인다.
- Null 허용 여부
- 동기화 여부
- iterator/Enumeration
HashMap은 Key나 Value에 Null 값을 허용하지만 HashTable이나 ConcurrentHashMap은 Null을 허용하지 않는다.
그리고 내부 아이템을 순회하기 위한 기능을 제공하는데, 그 부분에 대해서도 차이가 있다.(자세한 내용은 아래 링크 참조)
여기서는 HashMap, HashTable, ConcurrentHashMap에서 동기화 처리가 어떻게 다른지에 대해서 알아보도록 하자.
HashMap
HashMap은 Thread-Safe하지 못한 클래스이다. 따라서 MultiThread 환경에서 사용하려면 꼭 동기화처리가 필요하다.
Collections.synchronizedMap(hashMap) 등을 이용하여 동기화 처리 후 사용하자.
Hashtable
get(), put() 메서드를 확인해보면 synchronized하게 구현되어 있음을 확인할 수 있다.
public synchronized V get(Object key) { public synchronized V put(K key, V value) { } |
즉, Hashtable은 동기화 처리가 되어 있기 때문에 MultiThread 환경에서도 안전하게 사용할 수 있다.
ConcurrentHashMap
ConcurrentHashMap은 Hashtable과는 조금 차이가 있다. 먼저 putVal() 메서드 구현 부분을 확인해 보자.(아래 코드 참조)
기본적으로 put() 메서드에 synchronized 키워드가 없다. 아래 코드를 보면 동일 Entry(아래 코드에서는 Node)에 추가 될 경우 해당 Entry에 대해서 synchronized를 적용한 것을 확인할 수 있다. 즉, Entry 배열 아이템 별로 동기화 처리를 하고 있는 것이다. 이런 방식은 MultiThread 환경에서 성능을 크게 향상시킨다.
final V putVal(K key, V value, boolean onlyIfAbsent) { } |
MultiThread 환경 테스트
아래 코드는 http://jdm.kr/blog/197 에서 가져왔다. MultiThread 환경에서 각 자료구조에 put() 메서드를 실행해 보는 테스트 케이스이다. 종료 후 결과를 보면 HashMap만 Entry 사이즈가 다른 것을 확인 할 수 있다.
import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class MultiThreadsTest { private static final int MAX_THREADS = 10; private static Hashtable<String, Integer> ht = new Hashtable<>(); private static HashMap<String, Integer> hm = new HashMap<>(); private static HashMap<String, Integer> hmSyn = new HashMap<>(); private static Map<String, Integer> hmSyn2 = Collections.synchronizedMap(new HashMap<String, Integer>()); private static ConcurrentHashMap<String, Integer> chm = new ConcurrentHashMap<>(); public static void main(String[] args) throws InterruptedException { ExecutorService es = Executors.newFixedThreadPool(MAX_THREADS); for( int j = 0 ; j < MAX_THREADS; j++ ){ es.execute(new Runnable() { @Override public void run() { for( int i = 0; i < 1000; i++ ){ String key = String.valueOf(i); ht.put(key, i); hm.put(key, i); chm.put(key, i); hmSyn2.put(key, i); synchronized (hmSyn) { hmSyn.put(key, i); } } } }); } es.shutdown(); try { es.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Hashtable size is "+ ht.size()); System.out.println("HashMap size is "+ hm.size()); System.out.println("ConcurrentHashMap size is "+ chm.size()); System.out.println("HashMap(synchronized) size is "+ hmSyn.size()); System.out.println("synchronizedMap size is "+ hmSyn2.size()); /* for( String s : hm.keySet() ){ System.out.println("["+s+"] " + hm.get(s)); } */ } } Hashtable size is 1000 HashMap size is 1281 ConcurrentHashMap size is 1000 HashMap(synchronized) size is 1000 synchronizedMap size is 1000 |
결론
MultiThread 환경에서 동기화 처리가 필요하다면 ConcurrentHashMap을 사용하는 것이 안정적이다. 동기화가 필요하지 않은 경우라면 그냥 HashMap을 사용하자. Hashtable은 Thread-Safe 하긴 하나 느리다는 단점이 있다.
참고
'Programing > Java' 카테고리의 다른 글
상속 구조의 모델 객체에서 Lombok의 @Builder, @Delegate 사용기 (0) | 2017.04.21 |
---|---|
Lombok @Builder 사용시 기본값 지정하기 (0) | 2017.04.12 |
HashMap, TreeMap 그리고 LinkedHashMap의 차이 (1) | 2016.12.19 |
ConnectionTimeout과 SocketTimeout의 차이 (0) | 2016.11.18 |
P를 출력하는 프로그램 P (0) | 2016.11.10 |