Hinzufügen zu AtomicInteger innerhalb von ConcurrentHashMap - Java, Multithreading, Parallelität, java.util.concurrent, concurrenthashmap

Ich habe folgendes definiert

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

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

Nach dem Testen sind die Werte, die ich bekomme, nichterwartet, und ich denke, dass hier eine Race-Bedingung ist. Weiß jemand, ob dies als threadsicher angesehen wird, indem der Get-Call in die Replace-Funktion eingebunden wird?

Antworten:

2 für die Antwort № 1

Es gibt ein paar Probleme mit Ihrem Code. Das größte ist, dass Sie den Rückgabewert von. Ignorieren ConcurrentHashMap.replace: Wenn die Ersetzung nicht stattfindet (weil ein anderer Thread parallel eine Ersetzung durchgeführt hat), fahren Sie einfach fort als ob es passierte. Dies ist der Hauptgrund, warum Sie falsche Ergebnisse erhalten.

Ich denke auch, dass es ein Designfehler ist, einen zu mutieren AtomicInteger und dann sofort durch ein anderes ersetzen AtomicInteger; Selbst wenn Sie das funktionieren lassen können, gibt es einfach keinen Grund dafür.

Schließlich denke ich nicht, dass du anrufen solltest staffValues.get(100) zweimal. Ich glaube nicht, dass dies einen Fehler im aktuellen Code verursacht - Ihre Korrektheit hängt nur davon ab, ob der zweite Aufruf ein "neueres" Ergebnis liefert als das erste, was ich denke ist tatsächlich garantiert von ConcurrentHashMap - aber es ist fragil und subtil und verwirrend. Im Allgemeinen, wenn Sie anrufen ConcurrentHashMap.replace, sollte das dritte Argument etwas sein, das Sie mit dem zweiten Argument berechnet haben.

Insgesamt können Sie Ihren Code vereinfachen, indem Sie ihn nicht verwenden AtomicInteger:

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

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

oder indem du nicht benutzt replace (und vielleicht nicht einmal ConcurrentMap, je nachdem, wie du diese Karte berührst:

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

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

1 für die Antwort № 2

Eine gute Möglichkeit, mit solchen Situationen umzugehen, ist die Verwendung der computeIfAbsent Methode (nicht die compute Methode, die @ the8472 empfiehlt)

Das computeIfAbsent akzeptiert 2 Argumente, den Schlüssel und a Function<K, V> Das wird nur aufgerufen, wenn der vorhandene Wert fehlt. Da ein AtomicInteger threadsicher ist, um aus mehreren Threads zu inkrementieren, können Sie ihn auf folgende Weise problemlos verwenden:

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

0 für die Antwort № 3

Sie müssen nicht verwenden replace(). AtomicInteger ist ein veränderbarer Wert, der nicht ersetzt werden muss, wenn Sie ihn erhöhen möchten. Eigentlich addAndGet erhöht es bereits an Ort und Stelle.

Stattdessen verwenden compute um einen Standardwert (vermutlich 0) in die Karte zu setzen, wenn keiner vorhanden ist, und andernfalls den bereits vorhandenen Wert zu erhalten und diesen zu erhöhen.

Wenn Sie andererseits unveränderliche Werte verwenden möchten, setzen Sie Integer Instanzen statt AtomicInteger in die Karte und aktualisieren Sie sie mit den atomaren Rechen- / Ersetzen / Zusammenführen-Operationen.