ConcurrentMap — 스레드 안전·원자성 보장 맵
ConcurrentMap — 스레드 안전·원자성 보장 맵
ConcurrentMap<K,V>는 스레드 안전성과 원자성 보장을 제공하는 Map이에요. 다중 스레드가 같은 맵을 동시에 수정할 때 조건부 연산들이 하나의 원자적 단계로 수행되는 걸 보장하는 인터페이스예요.
본문
개념 이해하기
ConcurrentMap은 스레드 안전성과 원자성 보장을 제공하는 Map이에요.
public interface ConcurrentMap<K,V>
extends Map<K,V>
보장을 유지하기 위해 Map에서 상속받은 putIfAbsent(K,V) 같은 메서드의 기본 구현은 이 인터페이스의 구현체에서 오버라이드되어야 해요. 마찬가지로 Map.keySet(), Map.values(), Map.entrySet()이 반환하는 컬렉션의 구현체들도 원자성 보장을 유지하기 위해 필요시 removeIf 같은 메서드를 오버라이드해야 해요.
대표 구현체로 ConcurrentHashMap, ConcurrentSkipListMap이 있어요. ConcurrentNavigableMap이 하위 인터페이스예요.
메모리 일관성 효과: 다른 동시성 컬렉션처럼, 한 스레드가 ConcurrentMap에 키나 값으로 객체를 넣기 전의 동작은 다른 스레드가 그 객체를 접근·제거한 이후의 동작보다 happen-before 관계예요.
조회·순회
default V getOrDefault(Object key, V defaultValue) — 키에 매핑된 값 또는 defaultValue를 반환해요. (JDK 1.8+)
ClassCastException(선택),NullPointerException(선택)
default void forEach(BiConsumer<? super K,? super V> action) — 각 엔트리에 대해 액션을 수행해요. 엔트리 셋 순회 순서대로 수행돼요. (JDK 1.8+)
NullPointerException
조건부 원자 연산
V putIfAbsent(K key, V value) — 키가 아직 값과 연관되지 않았다면 주어진 값과 연관시켜요. 아래 코드와 동등하지만 원자적으로 수행돼요:
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
이전 값 또는 null을 반환해요.
UnsupportedOperationException,ClassCastException,NullPointerException,IllegalArgumentException
boolean remove(Object key, Object value) — 키가 주어진 값에 매핑돼 있을 때만 엔트리를 제거해요. 원자적. 값이 제거됐으면 true.
boolean replace(K key, V oldValue, V newValue) — 키가 주어진 값에 매핑돼 있을 때만 교체해요. 원자적. 교체됐으면 true.
V replace(K key, V value) — 키가 어떤 값에 매핑돼 있을 때만 교체해요. 원자적. 이전 값 또는 null을 반환해요.
replaceAll
default void replaceAll(BiFunction<? super K,? super V,? extends V> function) — 각 엔트리의 값을 함수를 적용한 결과로 교체해요. 여러 스레드가 업데이트를 시도하면 기본 구현이 단계를 재시도하며, 주어진 키에 대해 함수를 반복 호출할 수 있어요. (JDK 1.8+)
UnsupportedOperationException,NullPointerException,ClassCastException,IllegalArgumentException
compute 계열
default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) — 키가 값과 연관되지 않았거나 null에 매핑됐다면, 매핑 함수로 값을 계산해 null이 아니면 넣어요. 함수가 null을 반환하면 매핑을 기록하지 않아요. 가장 흔한 용도는 초기 매핑 값이나 메모이제이션 결과로 쓸 새 객체 생성이에요:
map.computeIfAbsent(key, k -> new Value(f(k)));
또는 다중 값 맵 Map<K,Collection<V>>을 구현할 때:
map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
UnsupportedOperationException,ClassCastException,NullPointerException,IllegalArgumentException(선택)
default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) — 키의 값이 있고 non-null이면 새 매핑을 계산해요. 함수가 null을 반환하면 매핑을 제거해요.
default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) — 키와 현재 값(없으면 null)으로 매핑을 계산해요. 값을 만들거나 추가하는 예:
map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))
함수가 null을 반환하면 매핑을 제거하거나(처음에 없었다면) 계속 없게 해요.
merge
default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) — 키가 값(또는 null)과 연관되지 않았다면 주어진 non-null value와 연관시키고, 그렇지 않으면 함수 결과로 교체하거나 null이면 제거해요. 여러 매핑 값을 결합할 때 유용해요:
map.merge(key, msg, String::concat)
UnsupportedOperationException,ClassCastException,NullPointerException,IllegalArgumentException(선택)