ThreadLocal.hashCode有多厉害

大家好, 这里是 K 字的研究. 今天我们继续深入研究下ThreadLocal.

上回书说道,ThreadLocal到底哪里好. 最重要的是两点:

  1. ThreadLocalMap被嵌入到了Thread, 从一个Thread对象找到他对应的Map,不可能有任何手段比这个更快了. 的 Hash 都不行.
  2. 内部ThreadLocalMapThreadLocal对象key,他有一个十分优秀的哈希函数.

第一条好处, 这个面子我们估计是没有,就不分析了.

第二条,这个哈希函数有多好, 上回我说了个不清不楚.估计诸位看官也闹了个糊里糊涂.所以, 这次老 K 准备了一点图, 来继续说道说道.

ThreadLocal 的 Hash 函数

ThreadLocal 里的 hashCode 写法如下:

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

逐行来解释下.

Java 类中的静态属性,会在类加载时候初始化. 这句会产生一个AtomicInteger 0

 private static AtomicInteger nextHashCode =
                             new AtomicInteger();

Java 类中的普通属性,会在new的时候初始化. 这句会获取一个hashCode

private final int threadLocalHashCode = nextHashCode();

AtomicInteger.getAndIncr会返回当前值, 并增加.

private static int nextHashCode() {
   return nextHashCode.getAndAdd(HASH_INCREMENT);
}

所有 ThreadLocal 变量的 hashCode 构成一个数列

有点太长了. 这里, 我们用 来代替 ,这个数列可以写作:

通项公式

很明显这是个递增的数列, 通项公式也很简单,

那么, 真的是这样吗? 当然不是,这个是理想中的公式. 但是现实中,Java 的数字是 32 个 bit 表示的有符号数,能表示的最大正数是 , 这个写法会溢出.

真实的公式, 其实相当于 , 最后的结果,强转成(int)类型.

把这个公式容易理解的Java写法写一下..

   for (int i = 0; i < 10; i++) {
      BigDecimal remainder = new BigDecimal(0x61c88647)
                    .multiply(new BigDecimal(i))
                    .remainder(new BigDecimal(1l << 32));
      System.out.println(remainder.intValue());
      System.out.println(0x61c88647*i);
}

输出结果

0==0
1640531527==1640531527
-1013904242==-1013904242
626627285==626627285
-2027808484==-2027808484
-387276957==-387276957
1253254570==1253254570
-1401181199==-1401181199
239350328==239350328
1879881855==1879881855

数列在int空间里的分布

把这个数列,画到32bit能表示的有限数轴上, 是什么样子呢? 请看.

ThreadLocal.hashCode有多厉害
Java int 数轴上的数列

这个就是ThreadLocalhashCode数列的可视化. 实现的比较好的hashCode都应该像这样, 在整个int的空间里,较为均匀的分布.

接下来添加几个比较一般的hashCode实现.这里还沿用 的通项公式,但是把常数从0x61c88647 加上1000000,10000000,100000000, 并列4个画在一起对比下.

ThreadLocal.hashCode有多厉害
0x61c88647,1000000,10000000,100000000的数列

这张图, 应该能够表现出,对于这个 通项公式来说, 这个值,确实是能够让生成的数列更加均匀.

0x61c88647是怎么求出来的?

这个值的求法要使用一个叫做三距离定理的工具.

ThreadLocal.hashCode有多厉害

老K是没本事证明了,不过, 2008年时候, 曾经有一位大学生写了一篇有关这个定理的论文《三距离定理及其连分数表示》[1]。这篇论文在知网可以看到,感兴趣怎么证明的可以去下下来看看。

ThreadLocal.hashCode有多厉害
三距离定理及其连分数表示

最后可以得到的结论是, 当 时候可以做到对 的划分最为均匀.

当这个数值扩大

    BigDecimal divide = new BigDecimal(5).sqrt(MathContext.DECIMAL128).subtract(BigDecimal.ONE).divide(new BigDecimal(2));
        BigDecimal multiply = divide
                .multiply(new BigDecimal(1l << 32));
        System.out.println(multiply.longValue());
        System.out.println(multiply.intValue());
        multiply = divide.negate()
                .multiply(new BigDecimal(1l << 32));
        System.out.println(multiply.longValue());
        System.out.println(multiply.intValue());

就可以得到两个神奇的值.

用long表示, 就是上面的. 用int表示,就是下面的.

2654435769
-1640531527
-2654435769
1640531527

0x61c88647的出处,其实就是黄金分割率在在32位int数字空间里的一个映射. 如果我们的hashCode是一个short类型的话, 这个神奇的值, 就会变成40503. 如果是long的话是多少, 不妨自己想一想.

好了, 今天就水到这里. 大家早点睡. 这张是上面4个数列跑了一阵子以后的样子.

ThreadLocal.hashCode有多厉害


参考资料

[1]

《三距离定理及其连分数表示》: https://kns.cnki.net/kcms/detail/detail.aspx?dbName=CMFD2010&filename=2009227426.nh



原文始发于微信公众号(K字的研究):ThreadLocal.hashCode有多厉害

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24641.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!