ThreadLocal的魔数为什么是0x61c88647?
原创漠狐烟 发布于2019-12-15 12:53:31 阅读数 17 收藏
我们通过上一篇文章分析得知ThreadLocal用map就避免不了冲突的产生,为了解决散列表的冲突而引入的神奇的hash code: 0x61c88647,可以让生成出来的值或者说ThreadLocal的ID较为均匀地分布在2的幂大小的数组中。
碰撞避免和解决
-
只有一个ThreadLocal实例的时候,官方推荐的做法,声明为public static,当向threadLocal变量中设置多个值的时产生的碰撞,碰撞解决是通过开放定址法, 且是线性探测(linear-probe)
-
有多个ThreadLocal实例的时候,最极端的是每个线程都new一个ThreadLocal实例,此时利用特殊的哈希码0x61c88647大大降低碰撞的几率, 同时利用开放定址法处理碰撞
看ThreadLocal源码中这个哈希码出现的地方:
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and inheritableThreadLocals).
* The ThreadLocal objects act as keys, searched via threadLocalHashCode.
* This is a custom hash code (useful only within ThreadLocalMaps) that
* eliminates collisions in the common case where consecutively
* constructed ThreadLocals are used by the same threads,
* while remaining well-behaved in less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically.
* Starts at zero.
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
//table的长度必须是2的N次方
private Entry[] table;
每当创建ThreadLocal实例时这个值都会累加 0x61c88647, 目的在上面的注释中已经写的很清楚了:为了让哈希码能均匀的分布在2的N次方的数组里.ThreadLocalMap 中 Entry[] table 的大小必须是2的N次方(len = 2^N),那 len-1 的二进制表示就是低位连续的N个1也就是 threadLocalHashCode 的低N位 这样就能均匀的产生均匀的分布.写段程序验证下:
private static final int HASH_INCREMENT = 0x61c88647;
public static void main(String[] args) {
magicHash(16); //初始大小16
magicHash(32); //扩容一倍
}
private static void magicHash(int size){
int hashCode = 0;
for(int i=0;i<size;i++){
hashCode = i*HASH_INCREMENT+HASH_INCREMENT;
System.out.print((hashCode & (size-1))+" ");
}
System.out.println();
}
控制台打印结果:
拓展
这个魔数的选取与斐波那契散列有关,0x61c88647对应的十进制为1640531527。
斐波那契散列的乘数可以用(long) ((1L << 31) * (Math.sqrt(5) - 1))可以得到2654435769,如果把这个值给转为带符号的int,则会得到-1640531527。
换句话说
(1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1))得到的结果就是1640531527也就是0x61c88647
long l = (long) ((1L << 31) * (Math.sqrt(5) - 1));
System.out.println("32位无符号整数: " + l);
int i = (int) l;
System.out.println("32位有符号整数: " + i);