Dodawanie do AtomicInteger w ramach ConcurrentHashMap - java, wielowątkowość, współbieżność, java.util.concurrent, concurrenthashmap

Mam zdefiniowane następujące

private ConcurrentMap<Integer, AtomicInteger>  = new ConcurrentHashMap<Integer, AtomicInteger>();

private void add() {
staffValues.replace(100, staffValues.get(100), new AtomicInteger(staffValues.get(100).addAndGet(200)));
}

Po przetestowaniu wartości, które otrzymuję, nie sąspodziewany, i myślę, że jest tutaj sytuacja wyścigowa. Czy ktokolwiek wie, czy byłoby to uważane za wątki bezpieczne przez zawijanie wywołania get w funkcji replace?

Odpowiedzi:

2 dla odpowiedzi № 1

Jest kilka problemów z kodem. Największe jest to, że ignorujesz wartość zwrotu ConcurrentHashMap.replace: jeśli wymiana nie nastąpi (z powodu innego wątku, który dokonał wymiany równolegle), po prostu kontynuuj jak gdyby stało się. Jest to główny powód, dla którego uzyskujesz błędne wyniki.

Myślę też, że to błąd w projektowaniu mutacji AtomicInteger a następnie natychmiast zastąpić go innym AtomicInteger; nawet jeśli możesz sprawić, żeby to działało, po prostu nie ma po temu powodu.

Wreszcie, nie sądzę, że powinieneś zadzwonić staffValues.get(100) dwa razy. Nie wydaje mi się, że powoduje to błąd w aktualnym kodzie - twoja poprawność zależy tylko od drugiego połączenia, zwracając "nowszy" wynik niż pierwszy, który moim zdaniem jest faktycznie gwarantowane przez ConcurrentHashMap - ale jest kruchy, subtelny i mylący, generalnie, gdy dzwonisz ConcurrentHashMap.replaceTrzeci argument powinien być czymś, co oblicza się za pomocą drugiego.

Ogólnie rzecz biorąc, możesz uprościć swój kod, nie używając go AtomicInteger:

private ConcurrentMap<Integer, Integer> staffValues = new ConcurrentHashMap<>();

private void add() {
final Integer prevValue = staffValues.get(100);
staffValues.replace(100, prevValue, prevValue + 200);
}

lub nie używając replace (a może nawet nie ConcurrentMap, w zależności od tego, jak jeszcze dotykasz tej mapy):

private Map<Integer, AtomicInteger> staffValues = new HashMap<>();

private void add() {
staffValues.get(100).addAndGet(200);
}

1 dla odpowiedzi nr 2

Dobrym sposobem radzenia sobie z takimi sytuacjami jest używanie computeIfAbsent metoda (nie compute metoda zalecana przez @ the47472)

The computeIfAbsent akceptuje 2 argumenty, klucz i Function<K, V> to zostanie wywołane tylko wtedy, gdy brakuje istniejącej wartości. Ponieważ AtomicInteger można bezpiecznie przenosić z wielu wątków, można go używać w następujący sposób:

staffValues.computeIfAbsent(100, k -> new AtomicInteger(0)).addAndGet(200);

0 dla odpowiedzi № 3

Nie musisz tego używać replace(). AtomicInteger jest zmienną wartością, która nie musi być zastępowana za każdym razem, gdy chcesz ją zwiększyć. w rzeczywistości addAndGet już zwiększa to miejsce.

Zamiast tego użyj compute aby umieścić domyślną wartość (przypuszczalnie 0) na mapie, gdy żadna z nich nie jest obecna, aw przeciwnym razie uzyskać wcześniejszą wartość i zwiększyć ją.

Jeśli, z drugiej strony, chcesz użyć niezmiennych wartości Integer instances zamiast AtomicInteger do mapy i zaktualizuj je za pomocą operacji atomowej compute / replace / merge.


Powiązane pytania
Menu