83 votes

Étant donné que les HashMaps en jdk1.6 et surtout poser des problèmes avec multi = filetage, comment dois-je réparer mon code

J'ai récemment posé une question dans stackoverflow, puis trouvé la réponse. La question initiale était Que des mécanismes autres que des mutexs ou la collecte des ordures peut ralentir mon multi-thread programme java?

J'ai découvert à ma grande horreur, que la table de hachage a été modifié entre JDK1.6 et JDK1.7. Il a maintenant un bloc de code qui provoque tous les threads création HashMaps à synchroniser.

La ligne de code dans JDK1.7.0_10 est

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

Qui finit par appel

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

La recherche dans d'autres Jdk, je trouve que ce n'est pas présente dans JDK1.5.0_22, ou JDK1.6.0_26.

L'impact sur mon code est énorme. Il fait en sorte que lorsque je l'exécute sur 64 threads, je reçois de moins bonnes performances que lorsque je l'exécute sur 1 fil. Un JStack montre que la plupart des threads passent la plupart de leur temps à tourner en boucle dans un ordre Aléatoire.

Donc, il me semble avoir quelques options:

  • Réécrire mon code pour que je n'utilise pas la table de hachage, mais utiliser quelque chose de similaire
  • En quelque sorte, s'amuser avec les rt.jar et remplacer la table de hachage à l'intérieur
  • Mess avec le chemin de classe d'une certaine manière, de sorte que chaque thread possède sa propre version de la table de hachage

Avant de me lancer dans un de ces chemins (très chronophages et potentiellement un impact élevé), je me demandais si j'avais raté un truc évident. Pouvez-vous tout de débordement de pile personnes suggèrent ce qui est le meilleur chemin, ou peut-être d'identifier une nouvelle idée.

Merci pour l'aide

56voto

mduigou Points 742

Je suis l'auteur original de la pièce, qui est apparu dans 7u6, CR#7118743 : Alternative de Hachage pour la Chaîne de Hachage à base de Cartes.

Je vais reconnaître d'emblée que l'initialisation de hashSeed est un goulot d'étranglement, mais il n'en est pas un, nous nous attendions à être un problème puisqu'il n'arrive qu'une fois par Hachage de la Carte de l'instance. Pour que ce code soit un goulot d'étranglement que vous auriez à créer des centaines ou des milliers de hachage cartes par seconde. Ce n'est certainement pas typique. Est-il vraiment une raison valable pour que votre demande soit de faire ceci? Combien de temps ces hachage cartes de vivre?

Peu importe, nous allons étudier probablement de commutation à ThreadLocalRandom plutôt qu'au Hasard et, éventuellement, une variante de l'initialisation tardive comme suggéré par cambecc.

EDIT 3

Un correctif pour résoudre le goulot d'étranglement a été poussé à l'JDK7 mise à jour mercurial repo:

http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/b03bbdef3a88

Le correctif sera le cadre de la prochaine 7u40 libération et est déjà disponible en IcedTea 2.4 versions.

Près de finale versions de test de 7u40 sont disponibles ici:

https://jdk7.java.net/download.html

Commentaires sont toujours les bienvenus. Envoyer à http://mail.openjdk.java.net/mailman/listinfo/core-libs-dev pour être sûr qu'il sera vu par la openJDK devs.

30voto

cambecc Points 1490

Cela ressemble à un "bug", vous pouvez contourner. Il y a une propriété qui désactive la nouvelle "alternative de hachage":

jdk.map.althashing.threshold = -1

Toutefois, la désactivation de la variante de hachage n'est pas suffisante, car elle ne permet pas de désactiver la génération aléatoire de hachage de la graine (même si il devrait vraiment). Donc, même si vous désactivez la touche alt de hachage, vous avez toujours le fil de contention lors de hachage carte de l'instanciation.

Il est particulièrement désagréable de la façon de travailler, c'est à force de remplacer l'instance de Random de hash graines de la génération de votre propre version non synchronisée:

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

Pourquoi est-il (probablement) coffre-fort pour ce faire? Car alt hachage a été désactivé, à l'origine de l'aléatoire de hachage graines pour être ignoré. Donc, il n'est pas question que notre instance d' Random n'est pas en fait aléatoire. Comme toujours avec nasty hacks comme ça, merci de l'utiliser avec prudence.

(Merci à http://stackoverflow.com/a/3301720/1899721 pour le code qui définit static final champs).

--- Edit ---

FWIW, la modification suivante à l' HashMap permettrait d'éliminer le fil de contention lors de l'alt de hachage est désactivé:

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

Une approche similaire peut être utilisée pour ConcurrentHashMap, etc.

3voto

user1951832 Points 31

Il y a beaucoup d'applications qui permettent de créer un transitoire de la table de hachage par enregistrement dans les grandes applications de données. Cette analyseurs et sérialiseurs, par exemple. Mettre une synchronisation non synchronisées dans les collections de classes est une véritable chasse aux sorcières. À mon avis, cette situation est inacceptable et doit être fixé le plus tôt possible. La modification qui a été apparemment introduit dans 7u6, CR#7118743 devraient être annulées ou fixe sans nécessiter de synchronisation ou d'opération atomique.

En quelque sorte, c'est me rappelle de l'erreur colossale de faire StringBuffer et de Vecteur et de la table de hachage synchronisées dans le JDK 1.1/1.2. Les gens ont payé cher depuis des années pour cette erreur. Pas besoin de répéter cette expérience.

2voto

eis Points 14687

En supposant que votre modèle d'utilisation est raisonnable, vous aurez envie d'utiliser votre propre version de la table de hachage.

Ce morceau de code est là pour faire des collisions de hachage beaucoup plus difficile à cause, empêche les pirates de créer des problèmes de performances (détails) - en supposant que ce problème est déjà traité d'une autre façon, je ne pense pas que vous auriez besoin de synchronisation. Toutefois, indépendamment de si vous utilisez la synchronisation ou non, il semble que vous souhaitez utiliser votre propre version de la table de hachage de sorte que vous ne serait pas depent que beaucoup sur ce JDK arrive à fournir.

Donc, soit vous venez d'écrire normalement quelque chose de semblable et qui, ou de remplacer une classe dans le JDK. Pour la deuxième option, vous pouvez remplacer bootstrap classpath avec -Xbootclasspath/p: paramètre. Cela sera toutefois "contrevenir à la Java 2 Runtime Environment code binaire de licence" (source).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X