Sta*_*ura 83 java collections performance multithreading
我最近在stackoverflow中提出了一个问题,然后找到答案.最初的问题是除了互斥锁或垃圾收集之外的什么机制可以减缓我的多线程java程序?
我惊恐地发现HashMap已经在JDK1.6和JDK1.7之间进行了修改.它现在有一个代码块,可以使创建HashMaps的所有线程同步.
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);
Run Code Online (Sandbox Code Playgroud)
最终打电话给谁
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));
}
Run Code Online (Sandbox Code Playgroud)
查看其他JDK,我发现JDK1.5.0_22或JDK1.6.0_26中不存在这种情况.
对我的代码的影响是巨大的.它使得当我在64个线程上运行时,我获得的性能低于在1个线程上运行时的性能.一个JStack显示大多数线程花费大部分时间在Random循环中旋转.
所以我似乎有一些选择:
在我开始使用这些路径之前(所有这些路径看起来非常耗时并且可能产生很大影响),我想知道我是否错过了一个明显的技巧.任何人堆叠溢出的人都可以建议哪条路更好,或者找出新想法.
谢谢您的帮助
Mik*_*gou 56
我是7u6中出现的补丁的原作者,CR#7118743:使用基于散列图的字符串替代哈希.
我会事先承认hashSeed的初始化是一个瓶颈,但它不是我们预期的问题,因为每个Hash Map实例只发生一次.要使此代码成为瓶颈,您必须每秒创建数百或数千个哈希映射.这当然不典型.您的申请是否真的有正当理由这样做?这些哈希映射存活多长时间?
无论如何,我们可能会调查切换到ThreadLocalRandom而不是Random,并且可能是cambecc建议的延迟初始化的一些变体.
编辑3
对瓶颈的修复被推送到JDK7更新mercurial repo:
http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/b03bbdef3a88
该修复程序将成为即将发布的7u40版本的一部分,并已在IcedTea 2.4版本中提供.
可在此处获得7u40的最终测试版本:
https://jdk7.java.net/download.html
反馈仍然受到欢迎.发送到http://mail.openjdk.java.net/mailman/listinfo/core-libs-dev以确保openJDK开发者可以看到它.
cam*_*ecc 30
这看起来像你可以解决的"bug".有一个属性禁用新的"替代哈希"功能:
jdk.map.althashing.threshold = -1
Run Code Online (Sandbox Code Playgroud)
但是,禁用替代散列是不够的,因为它不会关闭随机散列种子的生成(尽管它确实应该).因此,即使您关闭alt散列,在散列映射实例化期间仍然存在线程争用.
解决此问题的一种特别讨厌的方法是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);
Run Code Online (Sandbox Code Playgroud)
为什么这样做(可能)是安全的?由于已禁用alt散列,因此会忽略随机散列种子.因此,我们的实例Random实际上并不是随机的并不重要.像往常一样有这样令人讨厌的黑客,请谨慎使用.
(感谢/sf/answers/231120431/获取设置静态最终字段的代码).
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();
Run Code Online (Sandbox Code Playgroud)
可以使用类似的方法ConcurrentHashMap等.
| 归档时间: |
|
| 查看次数: |
9598 次 |
| 最近记录: |