ThreadLocal的魔数为什么是0x61c88647?

我们通过上一篇文章分析得知ThreadLocal用map就避免不了冲突的产生,为了解决散列表的冲突而引入的神奇的hash code: 0x61c88647,可以让生成出来的值或者说ThreadLocal的ID较为均匀地分布在2的幂大小的数组中。
碰撞避免和解决

  1. 只有一个ThreadLocal实例的时候,官方推荐的做法,声明为public static,当向threadLocal变量中设置多个值的时产生的碰撞,碰撞解决是通过开放定址法, 且是线性探测(linear-probe)

  2. 有多个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();
    }

控制台打印结果:
result

拓展
这个魔数的选取与斐波那契散列有关,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);

result

你可能感兴趣的:(Java,ThreadLocal,原理,java)