ConcurrentMap의 putIfAbsent를 사용하기 전에 맵에 키가 포함되어 있는지 확인해야합니다.
여러 스레드에서 사용할 수있는 맵에 Java의 ConcurrentMap을 사용하고 있습니다. putIfAbsent는 훌륭한 방법이며 표준 맵 작업을 사용하는 것보다 읽기 / 쓰기가 훨씬 쉽습니다. 다음과 같은 코드가 있습니다.
ConcurrentMap<String, Set<X>> map = new ConcurrentHashMap<String, Set<X>>();
// ...
map.putIfAbsent(name, new HashSet<X>());
map.get(name).add(Y);
가독성 측면에서는 훌륭하지만 이미 맵에 있더라도 매번 새로운 HashSet을 생성해야합니다. 다음과 같이 작성할 수 있습니다.
if (!map.containsKey(name)) {
map.putIfAbsent(name, new HashSet<X>());
}
map.get(name).add(Y);
이 변경으로 인해 약간의 가독성이 떨어지지 만 매번 HashSet을 만들 필요가 없습니다. 이 경우 어느 것이 더 낫습니까? 나는 더 읽기 쉽기 때문에 첫 번째 것을 선호하는 경향이 있습니다. 두 번째는 더 나은 성능을 발휘하고 더 정확할 수 있습니다. 아마도 이것보다 더 좋은 방법이있을 것입니다.
이런 방식으로 putIfAbsent를 사용하는 가장 좋은 방법은 무엇입니까?
동시성은 어렵습니다. 간단한 잠금 대신 동시 맵을 사용하려는 경우에는 그렇게하는 것이 좋습니다. 실제로 필요 이상으로 조회를 수행하지 마십시오.
Set<X> set = map.get(name);
if (set == null) {
final Set<X> value = new HashSet<X>();
set = map.putIfAbsent(name, value);
if (set == null) {
set = value;
}
}
(일반적인 stackoverflow 면책 조항 : 머리 위를 벗어났습니다. 테스트되지 않았습니다. 컴파일되지 않았습니다. 등)
업데이트 : 1.8은에 computeIfAbsent
기본 메서드를 추가 했습니다 ConcurrentMap
( Map
그 구현이에서 잘못 될 것이기 때문에 흥미 롭습니다 ConcurrentMap
). (그리고 1.7은 "다이아몬드 연산자"를 추가했습니다 <>
.)
Set<X> set = map.computeIfAbsent(name, n -> new HashSet<>());
(참고,에 HashSet
포함 된 의 모든 작업의 스레드 안전성에 대한 책임은 귀하에게 있습니다 ConcurrentMap
.)
ConcurrentMap에 대한 API 사용이 진행되는 한 Tom의 대답은 정확합니다. putIfAbsent 사용을 피하는 대안은 제공된 함수로 값을 자동으로 채우고 모든 스레드 안전성을 처리하는 GoogleCollections / Guava MapMaker의 컴퓨팅 맵을 사용하는 것입니다. 실제로 키당 하나의 값만 생성하고 생성 기능이 비싸면 값을 사용할 수있을 때까지 동일한 키를 요청하는 다른 스레드가 차단됩니다.
Guava 11에서 편집 한 MapMaker는 더 이상 사용되지 않으며 Cache / LocalCache / CacheBuilder 항목으로 대체됩니다. 이것은 사용법이 조금 더 복잡하지만 기본적으로 동형입니다.
Eclipse Collections (이전 GS Collections ) MutableMap.getIfAbsentPut(K, Function0<? extends V>)
에서 사용할 수 있습니다 .
을 호출 get()
하고 null 검사를 수행 한 다음 호출하는 것보다 장점 putIfAbsent()
은 키의 hashCode를 한 번만 계산하고 해시 테이블에서 올바른 지점을 한 번만 찾는다는 것입니다. 같은 ConcurrentMaps org.eclipse.collections.impl.map.mutable.ConcurrentHashMap
에서의 구현은 getIfAbsentPut()
스레드로부터 안전하고 원자 적입니다.
import org.eclipse.collections.impl.map.mutable.ConcurrentHashMap;
...
ConcurrentHashMap<String, MyObject> map = new ConcurrentHashMap<>();
map.getIfAbsentPut("key", () -> someExpensiveComputation());
의 구현 org.eclipse.collections.impl.map.mutable.ConcurrentHashMap
은 진정으로 차단되지 않습니다. 불필요하게 팩토리 함수를 호출하지 않도록 모든 노력을 기울이지 만 경합 중에 두 번 이상 호출 될 가능성이 여전히 있습니다.
이 사실은 Java 8의 ConcurrentHashMap.computeIfAbsent(K, Function<? super K,? extends V>)
. 이 메소드에 대한 Javadoc은 다음과 같이 설명합니다.
전체 메서드 호출은 원자 적으로 수행되므로 함수는 키당 최대 한 번만 적용됩니다. 다른 스레드가이 맵에서 시도한 일부 업데이트 작업은 계산이 진행되는 동안 차단 될 수 있으므로 계산은 짧고 간단해야합니다.
참고 : 저는 Eclipse Collections의 커미터입니다.
각 스레드에 대해 미리 초기화 된 값을 유지하면 허용되는 답변을 개선 할 수 있습니다.
Set<X> initial = new HashSet<X>();
...
Set<X> set = map.putIfAbsent(name, initial);
if (set == null) {
set = initial;
initial = new HashSet<X>();
}
set.add(Y);
최근에 Set이 아닌 AtomicInteger 맵 값과 함께 이것을 사용했습니다.
5 년 이상 동안 아무도이 문제를 해결하기 위해 ThreadLocal 을 사용하는 솔루션을 언급하거나 게시하지 않았다는 것을 믿을 수 없습니다 . 이 페이지의 몇 가지 솔루션 은 스레드로부터 안전하지 않으며 엉성합니다.
이 특정 문제에 대해 ThreadLocals를 사용 하는 것은 동시성에 대한 모범 사례 로 간주 될 뿐만 아니라 스레드 경합 중 가비지 / 객체 생성을 최소화하기위한 것 입니다. 또한 엄청나게 깨끗한 코드입니다.
예를 들면 :
private final ThreadLocal<HashSet<X>>
threadCache = new ThreadLocal<HashSet<X>>() {
@Override
protected
HashSet<X> initialValue() {
return new HashSet<X>();
}
};
private final ConcurrentMap<String, Set<X>>
map = new ConcurrentHashMap<String, Set<X>>();
그리고 실제 논리 ...
// minimize object creation during thread contention
final Set<X> cached = threadCache.get();
Set<X> data = map.putIfAbsent("foo", cached);
if (data == null) {
// reset the cached value in the ThreadLocal
listCache.set(new HashSet<X>());
data = cached;
}
// make sure that the access to the set is thread safe
synchronized(data) {
data.add(object);
}
내 일반적인 근사치 :
public class ConcurrentHashMapWithInit<K, V> extends ConcurrentHashMap<K, V> {
private static final long serialVersionUID = 42L;
public V initIfAbsent(final K key) {
V value = get(key);
if (value == null) {
value = initialValue();
final V x = putIfAbsent(key, value);
value = (x != null) ? x : value;
}
return value;
}
protected V initialValue() {
return null;
}
}
그리고 사용의 예 :
public static void main(final String[] args) throws Throwable {
ConcurrentHashMapWithInit<String, HashSet<String>> map =
new ConcurrentHashMapWithInit<String, HashSet<String>>() {
private static final long serialVersionUID = 42L;
@Override
protected HashSet<String> initialValue() {
return new HashSet<String>();
}
};
map.initIfAbsent("s1").add("chao");
map.initIfAbsent("s2").add("bye");
System.out.println(map.toString());
}
'Nice programing' 카테고리의 다른 글
CString과 :: std :: string :: std :: wstring을 서로 변환하는 방법은 무엇입니까? (0) | 2020.11.04 |
---|---|
Python urllib2 : HTTPError 예외 중에도 콘텐츠 본문을 읽습니까? (0) | 2020.11.04 |
자바 스크립트 코드를 통해 체크 된 경우에도 체크 박스 클릭 이벤트를 트리거하는 방법은 무엇입니까? (0) | 2020.11.04 |
자바 스크립트 자산 내에서 Rails 도우미 메서드 사용 (0) | 2020.11.04 |
CSS로 테두리 너비를 어떻게 설정합니까? (0) | 2020.11.04 |