java - sheet - Da HashMaps in jdk1.6 und höher Probleme mit multi=threading verursachen, wie behebe ich meinen Code?




thread safe collection java (3)

Das sieht nach einem "Bug" aus, mit dem man umgehen kann. Es gibt eine Eigenschaft, die die neue "alternative Hashing" -Funktion deaktiviert:

jdk.map.althashing.threshold = -1

Es ist jedoch nicht ausreichend, alternatives Hashing zu deaktivieren, da die Generierung eines zufälligen Hash-Seeds nicht deaktiviert wird (obwohl dies wirklich der Fall sein sollte). Selbst wenn Sie Alt-Hashing deaktivieren, haben Sie während der Hash-Map-Instantiierung immer noch Thread-Konflikte.

Eine besonders unangenehme Möglichkeit, dies zu umgehen, ist, die Instanz von Random die für die Hash-Seed-Generierung verwendet wird, zwangsweise durch eine eigene nicht synchronisierte Version zu ersetzen:

// Create an instance of "Random" having no thread synchronization.
Random alwaysOne = new Random() {
    @Override
    protected int next(int bits) {
        return 1;
    }
};

// Get a handle to the static final field sun.misc.Hashing.Holder.SEED_MAKER
Class<?> clazz = Class.forName("sun.misc.Hashing$Holder");
Field field = clazz.getDeclaredField("SEED_MAKER");
field.setAccessible(true);

// Convince Java the field is not final.
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

// Set our custom instance of Random into the field.
field.set(null, alwaysOne);

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 nicht zufällig ist. Wie immer bei solch fiesen Hacks, bitte mit Vorsicht verwenden.

(Danke an https://stackoverflow.com/a/3301720/1899721 für den Code, der statische Endfelder setzt).

--- Bearbeiten ---

FWIW, die folgende Änderung von HashMap würde den Threadkonflikt beseitigen, wenn Alt-Hashing deaktiviert ist:

-   transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
+   transient final int hashSeed;

...

         useAltHashing = sun.misc.VM.isBooted() &&
                 (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
+        hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0;
         init();

Ein ähnlicher Ansatz kann für ConcurrentHashMap usw. verwendet werden.

Ich habe kürzlich eine Frage im Stackoverflow gestellt und dann die Antwort gefunden. Die erste Frage war: Welche Mechanismen außer Mutex oder Garbage Collection können mein Multi-threaded Java-Programm verlangsamen?

Ich habe zu meinem Entsetzen festgestellt, dass HashMap zwischen JDK1.6 und JDK1.7 geändert wurde. Es verfügt jetzt über einen Codeblock, der alle Threads verursacht, die HashMaps erstellen, um zu synchronisieren.

Die Codezeile in JDK1.7.0_10 ist

 /**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);

Das ruft am Ende an

 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));
 }    

In anderen JDKs finde ich, dass dies in JDK1.5.0_22 oder JDK1.6.0_26 nicht vorhanden ist.

Der Einfluss auf meinen Code ist enorm. Es macht es so, dass wenn ich auf 64 Threads laufe, bekomme ich weniger Leistung als wenn ich auf 1 Thread laufe. Ein JStack zeigt, dass die meisten Threads die meiste Zeit damit verbringen, sich in Random in dieser Schleife zu drehen.

Also habe ich scheinbar ein paar Optionen:

  • Schreiben Sie meinen Code neu, sodass ich HashMap nicht verwende, sondern etwas ähnliches
  • Irgendwie mische dich mit dem rt.jar herum und ersetze die hashmap darin
  • Vermische irgendwie mit dem Klassenpfad, damit jeder Thread seine eigene Version von HashMap bekommt

Bevor ich einen dieser Wege (alle sehen sehr zeitaufwendig und potentiell sehr stark) beginnen, habe ich mich gefragt, ob ich einen offensichtlichen Trick verpasst habe. Kann irgendjemand von euch Stack-Überlauf-Leute vorschlagen, welcher der bessere Weg ist, oder vielleicht eine neue Idee identifizieren?

Danke für die Hilfe


Es gibt viele Apps, die eine transiente HashMap pro Datensatz in Big Data-Anwendungen erstellen. Diese Parser und Serialisierer zum Beispiel. Synchronisieren in unsynchronisierte Auflistungsklassen ist eine echte Herausforderung. Meiner Meinung nach ist dies inakzeptabel und muss so schnell wie möglich behoben werden. Die Änderung, die anscheinend in 7u6, CR # 7118743 eingeführt wurde, sollte rückgängig gemacht oder behoben werden, ohne dass irgendeine Synchronisation oder atomare Operation erforderlich wäre.

Irgendwie erinnert mich das an den kolossalen Fehler, StringBuffer und Vector und HashTable in JDK 1.1 / 1.2 synchron zu machen. Die Leute zahlten jahrelang teuer für diesen Fehler. Keine Notwendigkeit, diese Erfahrung zu wiederholen.


Unter der Annahme, dass Ihr Nutzungsmuster angemessen ist, sollten Sie Ihre eigene Version von Hashmap verwenden.

Dieser Teil des Codes ist dazu da, Hash-Kollisionen viel schwieriger zu verursachen, was Angreifer daran hindert, Leistungsprobleme ( details ) zu erzeugen - vorausgesetzt, dieses Problem wird bereits auf andere Weise behandelt, ich glaube nicht, dass Sie überhaupt eine Synchronisation benötigen. Unabhängig davon, ob Sie die Synchronisierung verwenden oder nicht, scheint es, als würden Sie Ihre eigene Version von Hashmap verwenden, so dass Sie nicht so sehr davon abhängig sind, was JDK zur Verfügung stellt.

Entweder schreibst du einfach etwas ähnliches und drückst darauf, oder überschreibst eine Klasse in JDK. Um Letzteres zu tun, können Sie den Bootstrap-Klassenpfad mit -Xbootclasspath/p: parameter überschreiben. Dies wird jedoch "gegen die Java 2 Runtime Environment-Binärcode-Lizenz verstoßen" ( source ).





collections