鉴于jdk1.6及以上版本中的HashMaps导致multi = threading的问题,应该如何修复我的代码

我最近在stackoverflow中提出了一个问题,然后find答案。 最初的问题是除互斥锁或垃圾收集之外的什么机制能够减缓我的multithreadingJava程序?

我惊奇地发现HashMap已经在JDK1.6和JDK1.7之间进行了修改。 它现在有一段代码,可以使所有创buildHashMaps的线程同步。

JDK1.7.0_10中的代码行是

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

哪个结束呼叫

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

查看其他JDK,我发现JDK1.5.0_22或JDK1.6.0_26中不存在这个JDK。

对我的代码的影响是巨大的。 这使得当我在64个线程上运行时,性能比我在1个线程上运行时less。 一个JStack表明,大多数线程花费大部分时间在随机循环中旋转。

所以我似乎有一些select:

  • 重写我的代码,以便我不使用HashMap,但使用类似的东西
  • 不知怎的,乱七八糟的rt.jar,并replace它里面的hashmap
  • 以某种方式混淆了类path,所以每个线程都得到自己的HashMap版本

在我开始使用这些path之前(都显得非常耗时且可能有很高的影响),我想知道是否我错过了一个明显的窍门。 你们中的任何一个可以堆栈溢出吗?人们可以build议哪条path更好,或者可能找出一个新的想法。

谢谢您的帮助

我是7月6日出现的修补程序的原始作者,CR#7118743:用哈希表映射string的替代哈希。

首先我会承认,hashSeed的初始化是一个瓶颈,但并不是我们所期望的问题,因为它只发生在每个Hash Map实例上。 对于这个代码是一个瓶颈,你将不得不每秒创build数百或数千个散列映射。 这当然不是典型的。 你的应用程序真的有这个理由吗? 这些散列图生存多久?

无论如何,我们可能会研究切换到ThreadLocalRandom而不是随机,并可能由cambeccbuild议的一些延迟初始化的变体。

编辑3

对瓶颈的修复被推到JDK7更新mercurial回购:

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

该修补程序将成为即将发布的7u40版本的一部分,并已在IcedTea 2.4版本中提供。

7u40的最终testing版本可以在这里find:

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

反馈仍然欢迎。 将它发送到http://mail.openjdk.java.net/mailman/listinfo/core-libs-dev ,以确保它被openJDK开发者看到。

这看起来像一个“错误”,你可以解决。 有一个属性会禁用新的“替代哈希”function:

 jdk.map.althashing.threshold = -1 

但是,禁用替代哈希是不够的,因为它不closures随机哈希种子的生成(虽然它确实应该)。 所以,即使closuresalt散列,在散列映射实例化过程中仍然存在线程争用。

解决这个问题的一个特别讨厌的方法是用你自己的非同步版本强制replace用于散列种子生成的Random实例:

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

为什么(可能)这样做是安全的? 由于alt散列已被禁用,导致随机散列种子被忽略。 所以我们的Random实例并不是随机的。 像这样的讨厌的黑客一样,请谨慎使用。

(感谢https://stackoverflow.com/a/3301720/1899721设置静态最终字段的代码)。;

—编辑—

FWIW,以下更改为HashMap将消除禁用alt散列时的线程争用:

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

类似的方法可以用于ConcurrentHashMap

有大量的应用程序在大数据应用程序中创build每个logging的瞬态HashMap。 这个parsing器和序列化器,例如。 将任何同步放入不同步的集合类是一个真正的难题。 在我看来,这是不可接受的,需要尽快修复。 在7u6,CR#7118743中显然引入的改变应该被恢复或固定,而不需要任何同步或primefaces操作。

不知何故,这让我想起了在JDK 1.1 / 1.2中使StringBuffer和Vector与HashTable同步的巨大错误。 人们多年来为此付出了沉重的代价。 没有必要重复那个经验。

假设您的使用模式是合理的,您将需要使用自己的Hashmap版本。

那段代码在那里使得哈希碰撞更难以引起,防止攻击者创build性能问题( 细节 ) – 假设这个问题已经以其他方式处理了,我不认为你需要同步。 然而,如果你使用同步或不使用同步,似乎你会想使用你自己的Hashmap版本,所以你不会太依赖JDK提供的东西。

所以要么通常只是写一些类似的东西,指向那个,或者在JDK中重写一个类。 要做后者,可以用-Xbootclasspath/p:参数覆盖引导类path。 但是,这样做会违反Java 2 Runtime Environment二进制代码许可证( 源代码 )。