Wie stellen Sie die Aktualisierung BigDecimal innerhalb ConcurrentHashMap thread-sichere
Ich versuche eine Anwendung, die dauert ein paar journal-Einträge und die Summe berechnen.
Ist unten Weg, es zu tun ist thread/concurrency sicher, wenn es mehrere threads Aufruf der addToSum()
Methode. Ich möchte sicherstellen, dass jeder Aufruf aktualisiert das total richtig.
Wenn es nicht sicher ist, erklären Sie bitte, was muss ich tun, um thread-Sicherheit gewährleisten.
Muss ich synchronize
den get/put-oder gibt es eine bessere Möglichkeit?
private ConcurrentHashMap<String, BigDecimal> sumByAccount;
public void addToSum(String account, BigDecimal amount){
BigDecimal newSum = sumByAccount.get(account).add(amount);
sumByAccount.put(account, newSum);
}
Vielen Dank!
Update:
Danke an alle für die Antwort habe ich bereits bekommen, dass der code oben ist nicht thread-safe.
Dank Vint für die Annahme, die AtomicReference
als alternative zu synchronize
. Ich war mit AtomicInteger
zu halten, integer Summen vor, und ich Frage mich, ob es so etwas wie, dass für BigDecimal.
Ist das eine definitive Schlussfolgerung über das pro und Contra von beiden?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Können Sie verwenden, synchronisiert wie die anderen vorgeschlagen, aber wenn Sie möchten, eine minimal-blocking-Lösung können Sie versuchen
AtomicReference
als Speicher für die BigDecimal -Bearbeiten - ich werde erklären mehr:
Eine AtomicReference verwendet CAS, atomar weist einen einzelnen Verweis. Die Schleife sagt dieser.
Wenn das aktuelle Feld gespeichert AtomicReference ==
oldVal
[Ihre Position im Speicher, nicht Ihren Wert], dann ersetzen Sie den Wert des Feldes gespeichert in AtomicReference mitoldVal.add(amount)
. Nun wird jedes mal nach der for-Schleife aufrufen newSum.get() es wird die BigDecimal-Objekt, das Hinzugefügt wurde.Möchten Sie eine Schleife verwenden, da es möglich ist, zwei threads versuchen, hinzufügen, um die gleichen AtomicReference. Es kann passieren, dass ein thread erfolgreich ist und ein anderer thread schlägt fehl, wenn das passiert, einfach nochmal versuchen mit den neuen zusätzlichen Wert.
Mit moderaten thread Behauptung, dies wäre eine schnellere Umsetzung, mit hohem Streit-Sie sind besser mit
synchronized
newSum.get()
zweimal in den Zustand überprüfen, aber es könnte sich ändern zwischen den beiden anrufen. Sie sollten vielleicht mit einemdo { } while()
wo Sie nur rufenget
einmal.while(true) { BigDecimal oldVal = newSum.get(); BigDecimal newVal = oldVal.add(amount); if(newSum.compareAndSet(oldVal, newVal)) break; }
.synchronized
ist besser für high-Streit? Ich dachte, dass (ohne besonderen Grund), dass CAS immer besser wurde.compareAndSet
nichts zu tun und zurückfalse
. Also ich glaube, es ist technisch O. K.; aber ich Stimme mit dir trotzdem, dass es besser wäre, rufen Sieget
nur einmal, so dass der code leichter zu Grunde geht.AtomicReference.compareAndSet
undAtomicReference.get
compareAndSet
wäre kein Problem, da BigDecimal ist unveränderlich,oldVal.add(amount)
nicht ändernoldVal
.Ist nicht sicher, da die threads A und B könnten beide rufen
sumByAccount.get(account)
zur gleichen Zeit (mehr oder weniger), also weder wird man sehen, das Ergebnis der anderenadd(amount)
. Das ist, was passieren könnte, in dieser Reihenfolge:sumByAccount.get("accountX")
und bekommt (zum Beispiel) 10.0.sumByAccount.get("accountX")
und bekommt den gleichen Wert, den Faden Einer Tat: 10.0.newSum
um (sagen wir) 10.0 + 2.0 = 12.0.newSum
um (sagen wir) 10.0 + 5.0 = 15.0.sumByAccount.put("accountX", 12.0)
.sumByAccount.put("accountX", 15.0)
zu überschreiben, was Ein thread hat.Einen Weg, um dies zu beheben, setzen
synchronized
auf IhreaddToSum
Methode, oder wickeln Sie den Inhalt insynchronized(this)
odersynchronized(sumByAccount)
. Eine andere Weise, da die oben genannten Abfolge der Ereignisse, die passiert nur, wenn zwei threads aktualisieren Sie den gleichen account zur gleichen Zeit, vielleicht, zu synchronisieren, extern, basierend auf irgendeine Art vonAccount
Objekt. Ohne dass der rest der Programm-Logik, ich kann nicht sicher sein.Ihre Lösung ist nicht thread-sicher. Der Grund dafür ist, dass es möglich ist, eine Summe zu übersehen, da der Betrieb zu setzen, ist getrennt von der operation zu bekommen (also den neuen Wert, den Sie setzen in die map könnte vermissen eine Summe, die Hinzugefügt wird, in der gleichen Zeit).
Ist der sicherste Weg, das zu tun, was Sie wollen zu tun ist, synchronisieren Sie Ihre Methode.
Ja, Sie brauchen, um zu synchronisieren, da Sie sonst zwei threads jeweils den gleichen Wert für den gleichen Schlüssel), sagen und thread 1 add B und thread 2 fügt C an und speichern Sie es zurück. Das Ergebnis jetzt nicht sein A+B+C, aber A+B oder A+C.
Was Sie tun müssen, ist die Sperre auf etwas, das in den Ergänzungen. Synchronisieren auf get/put wird nicht helfen, es sei denn, Sie tun
aber wenn Sie das tun, dann werden Sie verhindern, dass threads, die von der Aktualisierung der Werte auch wenn es für die verschiedenen Tasten. Die Sie synchronisieren möchten, auf das Konto. Jedoch, synchronisieren von auf dem string scheint unsicher, wie es könnte zu einem Deadlock führen (Sie weiß nicht, was sonst sperrt den string). Können Sie erstellen Sie ein Konto-Objekt statt und verwenden, die für die sperren?