ConcurrentHashMap computeIfAbsent

8

In Java 8 wurde eine neue API computeIfAbsent eingeführt. Die Javadocs für ConcurrentHashMap's implementation davon Zustand:

  

Wenn der angegebene Schlüssel nicht bereits einem Wert zugeordnet ist, wird versucht, seinen Wert mithilfe der angegebenen Zuordnungsfunktion zu berechnen und in diese Map einzugeben, sofern nicht Null. Der gesamte Methodenaufruf wird atomar ausgeführt, sodass die Funktion höchstens einmal pro Schlüssel angewendet wird. Einige versuchte Aktualisierungsoperationen in dieser Map durch andere Threads werden möglicherweise blockiert, während die Berechnung ausgeführt wird. Daher sollte die Berechnung kurz und einfach sein und darf nicht versuchen, andere Zuordnungen dieser Map zu aktualisieren.

Also, was sagt es über das Sperren dieser Implementierung, wenn der Schlüssel bereits existiert und die Berechnung nicht benötigt wird? Wird die gesamte Methode computeIfAbsent wie in der Dokumentation angegeben synchronisiert, auch wenn keine Berechnung erforderlich ist oder nur der Aufruf der Zuordnungsfunktion synchronisiert wird, um den Aufruf der Funktion zweimal zu verhindern?

    
Vic 21.10.2014, 08:09
quelle

2 Antworten

8

Die Implementierung von ConcurrentHashMap ist ziemlich komplex Es wurde speziell entwickelt, um gleichzeitige Lesbarkeit zu ermöglichen und gleichzeitig die Aktualisierung der Konflikte zu minimieren. Auf einer sehr hohen Abstraktionsebene ist es als Bucket-Hash-Tabelle organisiert. Alle Leseoperationen müssen nicht gesperrt werden, und (unter Angabe des Javadocs) "gibt es keine Unterstützung für das Sperren der gesamten Tabelle auf eine Weise, die jeglichen Zugriff verhindert" . Um dies zu erreichen, ist das interne Design hochentwickelt (aber immer noch elegant), mit Schlüssel-Wert-Zuordnungen in Knoten, die auf verschiedene Arten angeordnet werden können (wie Listen oder ausgeglichene Bäume), um feinkörnige Sperren zu nutzen. Wenn Sie an Implementierungsdetails interessiert sind, können Sie sich auch die Quellcode .

Versuchen Sie, Ihre Fragen zu beantworten:

  

Also, was sagt es über das Sperren dieser Implementierung für den Fall wann   der Schlüssel existiert bereits und die Berechnung ist nicht nötig?

Es ist vernünftig zu denken, dass wie bei jeder Leseoperation keine Sperre erforderlich ist, um zu prüfen, ob der Schlüssel bereits existiert und die Mapping-Funktion nicht ausgeführt werden muss.

  

Ist die gesamte Methode computeIfAbsent synchronisiert wie in den Dokumenten angegeben?   auch wenn keine Berechnung benötigt wird oder nur der Mapping-Funktionsaufruf ist   synchronisiert, um den Aufruf der Funktion zweimal zu verhindern?

Nein, die Methode ist im Hinblick auf das Sperren nicht synchronisiert , aber vom Standpunkt des Aufrufers aus wird sie atomar ausgeführt (d. h. die Abbildungsfunktion wird höchstens einmal angewendet). Wenn der Schlüssel nicht gefunden wird, muss eine Aktualisierungsoperation unter Verwendung des Werts ausgeführt werden, der von der Zuordnungsfunktion berechnet wird, und eine Art von Sperrung ist beteiligt, während diese Funktion aufgerufen wird. Es ist vernünftig zu denken, dass eine solche Blockierung sehr feinkörnig ist und nur einen sehr kleinen Teil der Tabelle betrifft (naja, die spezifische Datenstruktur, wo der Schlüssel gespeichert werden muss) und das ist der Grund (Zitat aus dem Javadoc, Hervorhebung von mir) " einige versuchte Aktualisierungsoperationen anderer Threads können blockiert werden, während die Berechnung ausgeführt wird" .

    
Cristian Greco 21.10.2014, 09:36
quelle
6

Sie können Konflikte erhalten, wenn der Wert bereits existiert.

Wenn Sie sich den Quellcode von computeIfAbsent () ansehen, ist das ziemlich komplex, aber Sie sehen, dass die Überprüfung, ob der Wert bereits vorhanden ist, im synchronisierten Block liegt. Betrachten Sie diese alternative Version (die nicht atomar funktioniert):

%Vor%

Ich führte einen JMH-Test durch, bei dem diese alternative Implementierung mit dem Original verglichen wurde. Ich habe 20 Threads ausgeführt und eine ConcurrentHashMap mit 20 Werten verwendet, die bereits vorhanden waren . Jeder Thread würde alle 20 Werte verwenden. Der Test hat nur nur dann ausgeführt, wenn der Wert bereits existiert. Es lief auf OS X. Das Ergebnis (nach einer 2-minütigen Aufwärmphase) war

%Vor%

Ich habe auch versucht, das mit aktivierter Flugaufnahme auszuführen, und der Streit war deutlich sichtbar. Hier ist ein Beispiel für einen Stack-Trace:

%Vor%     
purplie 13.07.2016 22:17
quelle