xml
Java ConcurrentHashMap 클래스는 Concurrency Collection Classes의 일부입니다.
ConcurrentHashMap
는 해시 테이블 구현으로, 동시 검색 및 업데이트를 지원합니다. 이는 다중 스레드 환경에서 ConcurrentModificationException을 피하기 위해 사용됩니다.
Java ConcurrentHashMap 예제
java.util.concurrent
패키지에서 Java 1.5에서 도입된 동시 클래스는 컬렉션을 반복하는 동안 수정을 시도하면 ConcurrentModificationException
이 발생합니다. ConcurrentHashMap은 반복 중에 Map을 수정할 수 있게 해줍니다. ConcurrentHashMap 작업은 스레드로부터 안전합니다. ConcurrentHashMap은 키 및 값에 대해 null을 허용하지 않습니다.
package com.journaldev.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
//ConcurrentHashMap
Map<String,String> myMap = new ConcurrentHashMap<String,String>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
myMap.put("4", "1");
myMap.put("5", "1");
myMap.put("6", "1");
System.out.println("ConcurrentHashMap before iterator: "+myMap);
Iterator<String> it = myMap.keySet().iterator();
while(it.hasNext()){
String key = it.next();
if(key.equals("3")) myMap.put(key+"new", "new3");
}
System.out.println("ConcurrentHashMap after iterator: "+myMap);
//HashMap
myMap = new HashMap<String,String>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
myMap.put("4", "1");
myMap.put("5", "1");
myMap.put("6", "1");
System.out.println("HashMap before iterator: "+myMap);
Iterator<String> it1 = myMap.keySet().iterator();
while(it1.hasNext()){
String key = it1.next();
if(key.equals("3")) myMap.put(key+"new", "new3");
}
System.out.println("HashMap after iterator: "+myMap);
}
}
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)
ConcurrentHashMap은 맵에서의 새로운 항목을 동시에 처리하는 반면, HashMap은 ConcurrentModificationException
을 throw합니다. 예외 스택 트레이스를 자세히 살펴보면 다음 문장이 예외를 throw했음을 알 수 있습니다.
String key = it1.next();
이는 HashMap에 새로운 항목이 삽입되었지만 Iterator가 실패했다는 것을 의미합니다. 실제로 컬렉션 객체의 Iterator는 fail-fast합니다. 즉, 컬렉션 객체의 구조나 항목 수에 대한 어떠한 변경도 예외를 발생시킵니다.
Iterator는 컬렉션의 변경 내용을 어떻게 알 수 있을까요?
우리는 HashMap에서 키의 집합을 가져와서 그 위에서 반복하고 있습니다. HashMap에는 변경 횟수를 세는 변수가 있으며, Iterator는 다음 항목을 가져올 때 next() 함수를 호출할 때 이 변수를 사용합니다. HashMap.java
:
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
위의 코드를 약간 수정하여 새로운 항목을 삽입할 때 iterator 루프에서 빠져나올 수 있도록 해보겠습니다. put 호출 후에 break 문을 추가하기만 하면 됩니다.
if(key.equals("3")){
myMap.put(key+"new", "new3");
break;
}
위의 코드로 출력한 결과는 다음과 같습니다:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}
키 값이 수정된다면 어떻게 될까요?
그러면 새 항목을 추가하지 않고 기존의 키-값 쌍을 업데이트한다면 어떻게 될까요? 예외가 발생할까요? 원본 프로그램의 코드를 변경하여 확인해 보겠습니다.
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");
컬렉션이 수정되었지만 구조는 동일하므로 예외가 발생하지 않을 것입니다.
더 읽어보기
컬렉션 객체 및 반복자를 생성할 때 각괄호를 주목했나요? 이를 제네릭스라고 하며 런타임에서 ClassCastException을 방지하기 위해 컴파일 시간에 타입 검사를 수행할 때 매우 강력합니다. 자바 제네릭스 예제에서 제네릭스에 대해 더 자세히 알아보세요. 또한 자바 컬렉션 인터뷰 질문 및 자바 이터레이터 디자인 패턴을 읽어보시기를 권합니다.
더 많은 자바 컬렉션 예제는 GitHub 리포지토리에서 확인할 수 있습니다.
참고: API 문서
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java