본문 바로가기

개발&컴퓨터/JAVA & SPRING

SynchronizedMap과 ConcurrentHashMap

반응형

지금까지 대부분 포스팅은 실무위주로 사용 예를 들면서 많이 썼었는데, 이번 포스팅은 조금 개념 위주로 작성하였습니다. SynchronizedMap과 ConcurrentHashMap 개념에 대해서 설명합니다.

 

회사 일하면서 SynchronizedMap 이라는 Map을 사용하게 되었는데, 궁금하여 이와 관련된 자료를 조금 조사하여 정리한 내용입니다.

 

---------------------------------------------------------------------------------------------

데이터를 담기 위해서 다양한 데이터를 담을 수 있는 컨테이너가 필요합니다. 이를 설명한 대로 간단히 데이터 컨테이너(Data Container)라 합니다. 

 

참고로 학교 다닐 때, 많이 접했을 단순한 Stack, Queue 구조부터 시작하여 좀 더 복잡한 List, Tree, Set, Map 등 여러가지 형태의 자료 구조가 있습니다. 이러한 자료 구조를 기반으로 저장된 자료를 어떻게 접근하고, 검색하는지까지 확장된 개념을 데이터 컨테이너라고 보면 되겠습니다.

 

다시 한번 말하면, 단순한 자료 구조를 넘어 데이터 컨테이너는 데이터를 담는 기능을 하기도 하지만, 저장된 데이터를 어떻게 빠르게 검색하고, 접근하는지에 대한 것까지 포함하는 좀 더 넓은 개념입니다. (자료구조에 데이터 접근 및 검색에 대한 알고리즘이 추가되었다면 조금 이해가 빠를까요?^^)

 

데이터 컨테이너를 간단히 설명한 이유는 SynchronizedMap 도 데이터 컨테이너의 한 종류입니다. 이름에서 알 수 있듯이, SynchronizedMap은 Map에서 파생되고, 그렇기 때문의 Map의 특성(Key-Value 방식의 데이터 관리)도 그대로 가지고 있습니다.

 

 

JAVA 1.0 ~ 1.1 에서는 키-값(Key-Value) 타입의 데이터로 HashTable이라는 것을 제공하였습니다. 그런데 이 HashTable은 속도가 느리고, 데이터를 처리할 수 있는 메서드(Method) 기능이 상당히 부족하였습니다.

 

JAVA 1.2 가 등장하면서 HashMap을 비롯하여 다양한 Key-Value 타입의 클래스를 지원하기 시작하였습니다. 이와 함께 이러한 Key-Value 타입의 클래스들의 공통 인터페이스인 Map 인터페이스로 통합되었습니다. 예를 들면 JAVA 1.1까지 지원하던 HashTable 도 Key-Value 타입 기반이므로 Map으로 부터 파생되어 구현(implements)되게 되었습니다.

 

이 때 등장한 주요 Map 2가지가 있는데, 이는 다음과 같습니다.

* HashMap

* TreeMap

 

HashMap은 이름에서도 알 수 있듯이, Hash 알고리즘을 사용하여 데이터를 가져오는 속도(get하는 속도)가 매우 빠릅니다.

TreeMap 역시 이름에서 알 수 있듯이, Tree 형태로 정렬하여 데이터를 저장함으로 Data 정렬시에 상대적으로 유리합니다.

 

※ 여기서 궁금한 사항이 그럼 Java 1.1때까지의 HashTable 이  Java 1.2 로 넘어오면서 HashMap으로 이름만 바뀐것인가요?

그렇지 않습니다. HashTable과 HashMap은 서로 다른 Map 클래스입니다. 그리고 HashTable과 HashMap은 서로 큰 차이가 있습니다.

 

HashTable은 데이터 변경 메서드가 모두 동기화(Synchronized) 메서드로 선언되어 있습니다. 좀 더 설명을 하자면 메서드 호출 전에 쓰레드간 동기화 락(Lock)을 걸기 때문에 멀티 쓰레드(Multi-Thread) 환경에서도 데이터의 무결성을 보장합니다.

 

반면 HashMap은 동기화를 지원하지 않습니다. 즉 여러 쓰레드에서 동시에 HashMap에 접근하게 되면 데이터 무결성이 손상될 수 있습니다.

 

 

※ 그러면 HashTable 이 안전하고, 상대적으로 더 좋은 인터페이스 아닌가요? 왜 굳이 JAVA 1.2 버전에 HashMap 이 등장하게 된 것인가요?

맨 처음에 언급하였는데, HashTable은 속도가 느립니다. 그 이유가 데이터 무결성을 지키기 위한 동기화 때문입니다. 동기화 락 작업은 매우 느린 동작입니다. 즉 상대적으로 동기화 처리를 하지 않는 HashMap은 HashTable에 비해 매우 빠른 처리 속도를 가집니다.

 

그렇기 때문에 데이터 무결성을 우려할 필요가 없는 단일 쓰레드에서는 HashMap을 사용하는 것이 훨씬 효율적입니다. (JAVA 1.1까지는 HashTable만 제공하였기 때문에 단일 쓰레드에서도 락 작업을 수행하는 HashTable을 쓸 수 밖에 없었습니다.)

 

게다가 무결성 문제 발생 소지가 있음에도 불구하고 프로그래밍 상의 편의성 때문에 멀티쓰레드 환경에서도 HashTable 보다는 HashMap을 많이 선호합니다.

 

 

※ 단순히 프로그래밍 편의성 때문에 멀티쓰레드 환경에서 데이터 무결성까지 포기해가면서까지 HashMap을 사용하는 것이 용납이 되는 문제인지요?

데이터 무결성은 중요한 문제입니다. 무결성이 깨지게 되면 데이터의 신뢰도가 떨어지고, 올바르지 않은 동작을 하게 됩니다. 동일한 데이터를 요구하였는데, 사용자 A에게는 10이라는 결과를 주고, 사용자 B에게는 20이라는 결과를 주면 이미 이는 데이터로서의 가치가 없게 됩니다.

 

은행의 전산 시스템을 개발하는데 데이터 무결성을 보장하지 못한다면 그 시스템은 휴지조각 만큼의 값어치도 없을 것입니다.

 

 

※ 그러면 HashMap을 사용하고도 동기화 문제를 해결할 수 있는 것인가요?

이 때 등장하는 개념이 SynchronizedMap 입니다. 어떤 Map 인터페이스(동기화를 지원하지 않는 Map이더라도)를 사용하더라도 SynchronizedMap으로 랩핑(Wrapping)하여 주면 해당 Map객체는 동기화 맵(Synchronized Map)이 됩니다.

 

 

this.infos = Collections.synchronizedMap(new HashMap<Long, String>());

 

위는 단순 HashMap을 SynchronizedMap으로 랩핑한 코드입니다. (위와 같이만 사용하면 됩니다.)

이는 HashTable 인터페이스를 사용하는 것보다 더 빠른 처리 속도를 가집니다.

 

 

※ 더 새로운 인터페이스는 없을까요?

JAVA 1.5 부터는 ConcurrentUtil 이라는 인터페이스를 기본으로 제공합니다.

단일 쓰레드 환경이라면 그냥 HashMap을, 멀티쓰레드 환경이라면 SynchronizedMap 을 쓰면 된다고 위에 설명하였는데, JAVA 1.5 버전 이후부터는 멀티 쓰레드 환경에서는 SynchronizedMap 보다는 ConcurrentUtil이 제공하는 ConcurrentHashMap 클래스를 사용하는 추세입니다. (HashMap + SynchronizedMap 의 대안으로 보시면 되겠습니다.)

 

ConcurrentHashMap 은 SynchronizedMap 으로 감싸진 HashMap 이나 HashTable 보다 더 빠른 속도를 보이면서도 쓰레드 간의 동기화를 보장합니다. 이게 가능한 이유는 ConcurrentHashMap은 동기화 시, Map 전체에 동기화 락을 걸지 않고, Map을 여러 조각으로 쪼개어 부분부분 락을 거는 형태로 구현되어 있기 때문입니다. 그러한 이유로 특히 (멀티 쓰레드 환경에서) 쓰레드 간의 경쟁이 심한 경우, 훨씬 더 효율적인 성능을 보입니다.

 

 

 

 

 

반응형