• ThreadLocal的魔数为什么是0x61c88647?


    ThreadLocal的魔数为什么是0x61c88647?

    原创漠狐烟 发布于2019-12-15 12:53:31 阅读数 17  收藏

    展开

    我们通过上一篇文章分析得知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

  • 相关阅读:
    Python中append和extend的区别
    python学习 day19
    python学习 day18
    QT下编写使用for循环动态添加刻选择时间类型的按钮(记录一下)
    QT mingw编译器下使用snap7库与西门子200smart-PLC(网口)通信实现代码
    看着挺胖的大胖猫
    QT添加软键盘后,QDialog设置模态后软键盘没响应解决办法
    QT程序打包在别的电脑上运行提示“api-ms-win-crt-runtime-|1-1-0.dll"可能与您正在运行的Window版本不兼容。。。。。
    Qt使用WM_COPYDATA消息进行进程通信
    离线百度地图,QT添加按钮点击切换卫星地图和街道地图
  • 原文地址:https://www.cnblogs.com/grj001/p/12223147.html
Copyright © 2020-2023  润新知