Die javadoc erklärt eindeutig die Ursache:
Einige versuchten Update-Operationen an dieser Stelle durch andere Threads sein kann blockiert, während Berechnungen laufen, so sollte die Berechnung kurz und einfach sein und darf nicht versuchen, andere Zuordnungen von dieser Karte zu aktualisieren.
Sie haben nicht zu vergessen, dass ConcurrentHashMap
ausgelegt ist, einen Thread sichere Karte, einen Weg zu schaffen, die Verwendung, ohne das Sperren, wie es der Fall für ältere Threadsicherheit Map-Klassen als HashTable
ist.
Wenn eine Änderung auf der Karte auftritt, wird nur das betreffende Mapping und nicht die gesamte Map gesperrt.
ConcurrentHashMap
ist eine Hash-Tabelle für Updates vollständige Parallelität von Wiedergewinnungen und hohen erwarteten concurrency unterstützen.
computeIfAbsent()
ist eine neue Methode in Java hinzugefügt 8.
Wenn das heißt, wenn im Körper computeIfAbsent()
die bereits verriegelt die Abbildung der Schlüssel übergeben der Methode, Sie sperren einen anderen Schlüssel schlecht verwendet, Sie Geben Sie einen Pfad ein, in dem Sie den Zweck der ConcurrentHashMap
ablehnen können, da Sie schließlich zwei Mappings sperren.
Stellen Sie sich das Problem vor, wenn Sie mehr Mapping innerhalb computeIfAbsent()
sperren und dass die Methode überhaupt nicht kurz ist. Der Zugriff auf die Karte auf Nebenläufigkeit wird langsam.
So betont das Javadoc computeIfAbsent()
auf dieses potenzielle Problem durch das Erinnern der Prinzipien von ConcurrentHashMap
: halten Sie es einfach und schnell.
Hier ist ein Beispielcode, der das Problem veranschaulicht.
Angenommen, wir haben eine Instanz von ConcurrentHashMap<Integer, String>
.
Wir werden zwei Threads beginnen, die sie verwenden:
- Der erste Thread:
thread1
, die computeIfAbsent()
mit dem Schlüssel 1
- des zweiten Thread aufruft:
thread2
die computeIfAbsent()
mit dem Schlüssel 2
rufe
führt eine schnell genug Aufgabe, aber es folgt nicht beraten von die computeIfAbsent()
javadoc: es aktualisiert den Schlüssel 2
in computeIfAbsent()
, das ist eine andere Zuordnung von denen im aktuellen Kontext der Methode verwendet (das ist Schlüssel 1
).
thread2
führt eine ausreichend lange Aufgabe aus. Es ruft computeIfAbsent()
mit dem Schlüssel 2
auf, indem es den Javadoc-Anweisungen folgt: es aktualisiert kein anderes Mapping in der Implementierung von es.
Um die lange Aufgabe zu simulieren, können wir die Thread.sleep()
Methode mit 5000
als Parameter verwenden.
Für diese spezielle Situation, wenn thread2
beginnt vor thread1
, wird der Aufruf von map.put(2, someValue);
in thread1
während thread2
blockiert werden nicht von computeIfAbsent()
zurückgegeben, die die Zuordnung des Schlüssels 2
sperrt.
Schließlich erhalten wir ein ConcurrentHashMap
Beispiel blockiert die Abbildung der Taste 2
während 5 Sekunden, während computeIfAbsent()
mit der Abbildung des Schlüssel 1
aufgerufen wird.
Es ist irreführend, nicht wirksam und es geht gegen die ConcurrentHashMap
Absicht und der computeIfAbsent()
Beschreibung, die die Absicht für die aktuelle Taste, um den Wert der Berechnung:
wenn der angegebene Schlüssel nicht bereits mit einem Wert zugeordnet ist, versucht seinen Wert mit der gegebenen Abbildungsfunktion zu berechnen, und gibt sie in diese Karte sofern nicht null
Der Beispielcode:
import java.util.concurrent.ConcurrentHashMap;
public class BlockingCallOfComputeIfAbsentWithConcurrentHashMap {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
Thread thread1 = new Thread() {
@Override
public void run() {
map.computeIfAbsent(1, e -> {
String valueForKey2 = map.get(2);
System.out.println("thread1 : get() returns with value for key 2 = " + valueForKey2);
String oldValueForKey2 = map.put(2, "newValue");
System.out.println("thread1 : after put() returns, previous value for key 2 = " + oldValueForKey2);
return map.get(2);
});
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
map.computeIfAbsent(2, e -> {
try {
Thread.sleep(5000);
} catch (Exception e1) {
e1.printStackTrace();
}
String value = "valueSetByThread2";
System.out.println("thread2 : computeIfAbsent() returns with value for key 2 = " + value);
return value;
});
}
};
thread2.start();
Thread.sleep(1000);
thread1.start();
}
}
Als Ausgang wir immer bekommen:
thread1: get() gibt mit dem Wert für die Taste 2 = null
thread2: computeIfAbsent() gibt mit dem Wert für die Taste 2 = valueSetByThread2
thread1: nach put() zurückkehrt, vorherigen Wert für Taste 2 = valueSetByThread2
Dies ist writt en schnell wie auf den ConcurrentHashMap
liest nicht blockieren:
thread1: get() für die Taste 2 = null
mit Wert zurückgibt
aber:
thread1: nach put () zurück, vorheriger Wert für Taste 2 = valueSetByThread2
wird nur ausgegeben, wenn thread2 zurückgegeben wird von computeIfAbsent()
.
Eine solche Spezifikation sollte sehr sorgfältig gelesen werden. Die Wörter "darf nicht versuchen" zeigt an, dass Sie wahrscheinlich irgendwo etwas brechen, wenn Sie der Regel nicht folgen. Aber im Vergleich zu Andy interessiert mich immer sehr, warum solche Restriktionen eingeführt werden. –