Ich habe kürzlich eine Frage in stackoverflow gestellt und dann die Antwort gefunden. Die erste Frage war: Welche anderen Mechanismen als Mutexe oder Garbage Collection können mein Java-Programm mit mehreren Threads verlangsamen?
Zu meinem Entsetzen stellte ich fest, dass HashMap zwischen JDK1.6 und JDK1.7 geändert wurde. Es hat jetzt einen Codeblock, der bewirkt, dass alle Threads, die HashMaps erstellen, synchronisiert werden.
Die Codezeile in JDK1.7.0_10 lautet
/**A randomizing value associated with this instance that is applied to hash code of keys to make hash collisions harder to find. */
transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
Was am Ende anruft
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
Wenn ich in anderen JDKs nachschaue, finde ich, dass dies in JDK1.5.0_22 oder JDK1.6.0_26 nicht vorhanden ist.
Die Auswirkungen auf meinen Code sind enorm. Es macht es so, dass wenn ich auf 64 Threads laufe, ich weniger Leistung bekomme als wenn ich auf 1 Thread laufe. Ein JStack zeigt, dass die meisten Threads die meiste Zeit damit verbringen, sich in dieser Schleife in Random zu drehen.
Ich habe also einige Möglichkeiten:
- Schreiben Sie meinen Code neu, damit ich keine HashMap verwende, sondern etwas Ähnliches
- Spielen Sie irgendwie mit dem rt.jar herum und ersetzen Sie die darin enthaltene Hashmap
- Verwirren Sie sich irgendwie mit dem Klassenpfad, sodass jeder Thread seine eigene Version von HashMap erhält
Bevor ich einen dieser Pfade beschreite (alle sehen sehr zeitaufwändig und potenziell wirkungsvoll aus), habe ich mich gefragt, ob ich einen offensichtlichen Trick verpasst habe. Kann jemand von euch Überlauf-Leute vorschlagen, welcher der bessere Weg ist, oder vielleicht eine neue Idee identifizieren?
Danke für die Hilfe
quelle
AtomicLong
setzt auf geringe Schreibkonflikte, um gut zu funktionieren. Sie haben hohe Schreibkonflikte, daher benötigen Sie regelmäßige exklusive Sperren. Wenn Sie eine synchronisierteHashMap
Factory schreiben, werden Sie wahrscheinlich eine Verbesserung feststellen, es sei denn, Sie tun in diesen Threads nur eine Karteninstanziierung.Antworten:
Ich bin der ursprüngliche Autor des Patches, der in 7u6, CR # 7118743: Alternatives Hashing für String mit Hash-basierten Maps, veröffentlicht wurde.
Ich werde gleich zu Beginn anerkennen, dass die Initialisierung von hashSeed ein Engpass ist, aber wir haben nicht erwartet, dass dies ein Problem ist, da es nur einmal pro Hash Map-Instanz auftritt. Damit dieser Code ein Engpass ist, müssen Sie Hunderte oder Tausende von Hash-Maps pro Sekunde erstellen. Das ist sicherlich nicht typisch. Gibt es wirklich einen triftigen Grund für Ihre Bewerbung, dies zu tun? Wie lange leben diese Hash-Maps?
Unabhängig davon werden wir wahrscheinlich die Umstellung auf ThreadLocalRandom anstelle von Random und möglicherweise eine Variante der verzögerten Initialisierung untersuchen, wie von cambecc vorgeschlagen.
BEARBEITEN 3
Ein Fix für den Engpass wurde in das Quecksilber-Repo des JDK7-Updates verschoben:
http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/b03bbdef3a88
Das Update wird Teil der kommenden 7u40-Version sein und ist bereits in IcedTea 2.4-Versionen verfügbar.
Nahezu endgültige Testversionen von 7u40 sind hier verfügbar:
https://jdk7.java.net/download.html
Feedback ist weiterhin willkommen. Senden Sie es an http://mail.openjdk.java.net/mailman/listinfo/core-libs-dev , um sicherzustellen, dass es von den openJDK-Entwicklern gesehen wird.
quelle
Dies sieht aus wie ein "Fehler", den Sie umgehen können. Es gibt eine Eigenschaft, die die neue Funktion "Alternatives Hashing" deaktiviert:
Das Deaktivieren von alternativem Hashing ist jedoch nicht ausreichend, da dadurch die Erzeugung eines zufälligen Hash-Seeds nicht deaktiviert wird (obwohl dies eigentlich der Fall sein sollte). Selbst wenn Sie das Alt-Hashing deaktivieren, treten während der Instanziierung der Hash-Map immer noch Thread-Konflikte auf.
Eine besonders unangenehme Möglichkeit, dies zu umgehen, besteht darin, die
Random
für die Hash-Seed-Generierung verwendete Instanz durch Ihre eigene nicht synchronisierte Version zu ersetzen :Warum ist es (wahrscheinlich) sicher, dies zu tun? Da das Alt-Hashing deaktiviert wurde, werden die zufälligen Hash-Seeds ignoriert. Es spielt also keine Rolle, dass unsere Instanz von
Random
tatsächlich nicht zufällig ist. Wie immer bei solchen bösen Hacks, bitte mit Vorsicht verwenden.(Dank an https://stackoverflow.com/a/3301720/1899721 für den Code, der statische Endfelder festlegt).
--- Bearbeiten ---
FWIW, die folgende Änderung
HashMap
würde den Thread-Konflikt beseitigen, wenn Alt-Hashing deaktiviert ist:Ein ähnlicher Ansatz kann für
ConcurrentHashMap
usw. verwendet werden.quelle
Es gibt viele Apps, die in Big-Data-Anwendungen eine vorübergehende HashMap pro Datensatz erstellen. Dies sind zum Beispiel Parser und Serialisierer. Das Einfügen einer Synchronisation in nicht synchronisierte Sammlungsklassen ist ein echtes Problem. Meiner Meinung nach ist dies nicht akzeptabel und muss so schnell wie möglich behoben werden. Die Änderung, die anscheinend in 7u6, CR # 7118743 eingeführt wurde, sollte zurückgesetzt oder behoben werden, ohne dass eine Synchronisation oder eine atomare Operation erforderlich ist.
Irgendwie erinnert mich das an den kolossalen Fehler, StringBuffer und Vector und HashTable in JDK 1.1 / 1.2 synchronisiert zu haben. Die Leute haben jahrelang teuer für diesen Fehler bezahlt. Diese Erfahrung muss nicht wiederholt werden.
quelle
Vorausgesetzt, Ihr Nutzungsmuster ist angemessen, möchten Sie Ihre eigene Version von Hashmap verwenden.
Dieser Code dient dazu, Hash-Kollisionen viel schwerer zu verursachen und Angreifer daran zu hindern, Leistungsprobleme ( Details ) zu verursachen. Vorausgesetzt, dieses Problem wird bereits auf andere Weise behandelt, benötigen Sie meiner Meinung nach überhaupt keine Synchronisierung. Unabhängig davon, ob Sie die Synchronisierung verwenden oder nicht, möchten Sie anscheinend Ihre eigene Version von Hashmap verwenden, damit Sie nicht so sehr davon abhängen, was JDK gerade bereitstellt.
Entweder schreiben Sie normalerweise etwas Ähnliches und zeigen darauf oder Sie überschreiben eine Klasse in JDK. Um letzteres zu tun, können Sie den Bootstrap-Klassenpfad mit dem
-Xbootclasspath/p:
Parameter überschreiben . Dies würde jedoch "gegen die Java 2 Runtime Environment-Binärcodelizenz verstoßen" ( Quelle ).quelle