Rekursive ConcurrentHashMap.computeIfAbsent () - Aufruf nie beendet. Bug oder "feature"?
Einige Zeit her, Gebloggt habe ich über einen Java 8 funktionale Art und Weise der Berechnung der fibonacci-zahlen rekursiv, mit einem ConcurrentHashMap
cache und die neue, nützliche computeIfAbsent()
Methode:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Test {
static Map<Integer, Integer> cache = new ConcurrentHashMap<>();
public static void main(String[] args) {
System.out.println(
"f(" + 8 + ") = " + fibonacci(8));
}
static int fibonacci(int i) {
if (i == 0)
return i;
if (i == 1)
return 1;
return cache.computeIfAbsent(i, (key) -> {
System.out.println(
"Slow calculation of " + key);
return fibonacci(i - 2) + fibonacci(i - 1);
});
}
}
Wählte ich ConcurrentHashMap
weil ich dachte, dieses Beispiel noch raffinierter durch die Einführung von Parallelität (was ich nicht am Ende).
Nun vergrößern wir die Anzahl von 8
zu 25
und beobachten, was passiert:
System.out.println(
"f(" + 25 + ") = " + fibonacci(25));
Das Programm nie unterbrochen. Innerhalb der Methode gibt es eine Schleife, die läuft einfach immer:
for (Node<K,V>[] tab = table;;) {
//...
}
Ich bin mit:
C:\Users\Lukas>java -version
java version "1.8.0_40-ea"
Java(TM) SE Runtime Environment (build 1.8.0_40-ea-b23)
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)
Matthias, ein Leser von diesem blog-post auch bestätigt das Problem (er fand es sogar).
Dies ist seltsam. Ich hätte erwartet, dass eine der folgenden zwei:
- Es funktioniert
- Es wirft ein
ConcurrentModificationException
Aber eben nie aufzuhalten? Das scheint gefährlich. Ist es ein bug? Oder habe ich missverstanden, einige Vertrag?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Dies ist fest in JDK-8062841.
In der Vorschlag von 2011, identifizierte ich dieses Problem bei der code-überprüfung. Die JavaDoc aktualisiert wurde und ein temporärer fix wurde Hinzugefügt. Es wurde entfernt, in einer weiteren rewrite-aufgrund der performance-Probleme.
In der 2014-Diskussion, wir erkunden Wege, um besser zu erkennen und scheitern. Beachten Sie, dass einige der Diskussion wurde offline genommen, um private E-Mail für die Prüfung der low-level-änderungen. Während nicht jeder Fall abgedeckt werden kann, die in häufigen Fällen nicht livelock. Diese behebt sind in Doug ' s repository, aber haben es nicht geschafft in das JDK-release.
transfer
und es war eine verpasste Fall. Können Sie E-Mail -[email protected]
um Doug ' s Aufmerksamkeit?AsyncLoadingCache
so dass die Berechnungen durchgeführt werden, die außerhalb der Karte operation (auf die Zukunft), wahlweise mit dersynchronous()
anzeigen.Dies ist natürlich eine "feature". Die
ConcurrentHashMap.computeIfAbsent()
Javadoc liest:Den "muss nicht" Formulierung ist eine klare Vereinbarung, die mein Algorithmus verletzt, zwar nicht für die gleiche Parallelität Gründen.
Was noch interessant ist, dass es keine
ConcurrentModificationException
. Stattdessen wird das Programm nur nie die halte - was immer noch ein ziemlich gefährlicher Fehler meiner Meinung nach (d.h. Endlosschleifen. oder: alles kann schief gehen, tut).Hinweis:
Den
HashMap.computeIfAbsent()
oderGekennzeichnet.computeIfAbsent()
Javadoc nicht verbieten, eine solche rekursive Berechnung, was natürlich lächerlich ist, wie die Art des Caches istMap<Integer, Integer>
, nichtConcurrentHashMap<Integer, Integer>
. Es ist sehr gefährlich für die Subtypen drastisch zu re-definieren super Typ Verträgen (Set
vs.SortedSet
ist Gruß). Sollte es daher verboten werden, auch in super-Typen, führen solche Rekursion.ConcurrentHashMap
weil der andere wichtige Bestandteil seines Vertrags: "Das gesamte Methodenaufruf erfolgt atomar, so wird die Funktion angewendet wird höchstens einmal pro Schlüssel." Es ist wahrscheinlich, dass Sie Ihr Programm, indem Sie die Verletzung der "keine rekursiven modifiction" Vertrag, der versucht, eine Sperre erwerben, die es bereits hält, und ist Deadlock-mit sich selbst, nicht läuft in einer Endlosschleife.IllegalStateException - if the computation detectably attempts a recursive update to this map that would otherwise never complete
HashMap
es ist nicht OK. bugs.openjdk.java.net/browse/JDK-8172951HashMap
es funktioniert". Wir formulieren es so: "obwohlMap
javadocs nicht explizit verbieten, dass die Funktion sollte nicht ändern Sie die Karte in irgendeiner Weise"Dies ist sehr ähnlich zu dem Fehler.
Weil, wenn Sie erstellen Sie Ihre cache mit einer Kapazität 32, funktioniert zwar das Programm, bis 49.
Und es ist interessant, dass der parameter sizeCtl =32 + (32 >>> 1) + 1) =49!
Kann der Grund in der Größenänderung?
CHM
anfänglichen Kapazität zu einer sehr hohen Zahl, das ging Weg. Bis wir überarbeiten unseren code, das ist, was wir tun...