웹사이트 검색

Java의 ConcurrentHashMap


Java ConcurrentHashMap 클래스는 동시성 컬렉션 클래스의 일부입니다. 동시 검색 및 업데이트를 지원하는 해시 테이블 구현입니다. ConcurrentModificationException을 피하기 위해 다중 스레드 환경에서 사용됩니다.

ConcurrentHashMap

반복하는 동안 컬렉션을 수정하려고 하면 ConcurrentModificationException이 발생합니다. Java 1.5는 이 시나리오를 극복하기 위해 java.util.concurrent 패키지에 Concurrent 클래스를 도입했습니다. ConcurrentHashMap은 반복하는 동안 Map을 수정할 수 있게 해주는 Map 구현입니다. ConcurrentHashMap 작업은 스레드로부터 안전합니다. ConcurrentHashMap은 키와 값에 대해 null을 허용하지 않습니다.

자바 ConcurrentHashMap 예제

ConcurrentHashMap 클래스는 스레드로부터 안전하고 반복하는 동안 수정이 가능하다는 점을 제외하면 HashMap과 유사합니다.

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을 발생시키는 것이 출력에서 분명합니다. 예외 스택 추적을 자세히 살펴보겠습니다. 다음 문에서 예외가 발생했습니다.

String key = it1.next();

새 항목이 HashMap에 삽입되었지만 Iterator가 실패했음을 의미합니다. 사실, Collection 객체의 Iterator는 fail-fast입니다. 즉, 구조를 수정하거나 컬렉션 객체의 항목 수를 수정하면 예외가 트리거됩니다.

반복자는 컬렉션의 수정 사항에 대해 어떻게 알 수 있습니까?

우리는 HashMap에서 키 세트를 가져온 다음 이를 반복했습니다. HashMap에는 수정 횟수를 세는 변수가 포함되어 있으며 반복자는 다음 항목을 가져오기 위해 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;

새 항목을 삽입할 때 반복자 루프에서 나오도록 코드를 약간 변경해 보겠습니다. 우리가 해야 할 일은 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");

컬렉션이 수정되었지만 구조는 동일하게 유지되기 때문에 예외는 없습니다.

추가 자료

컬렉션 개체와 Iterator를 만드는 동안 이러한 꺾쇠 괄호를 보셨습니까? 제네릭이라고 하며 런타임에 ClassCastException을 제거하기 위해 컴파일 타임에 유형 검사를 할 때 매우 강력합니다. Iterator Design Pattern in Java에서 제네릭에 대해 자세히 알아보세요.

GitHub 리포지토리에서 더 많은 Java 컬렉션 예제를 확인할 수 있습니다.

참조: API 문서