大家好, 这里是 K 字的研究. 今天我们继续深入研究下ThreadLocal
.
上回书说道,ThreadLocal
到底哪里好. 最重要的是两点:
-
ThreadLocalMap
被嵌入到了Thread
, 从一个Thread对象
找到他对应的Map
,不可能有任何手段比这个更快了. 的 Hash 都不行. -
内部 ThreadLocalMap
以ThreadLocal对象
做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
数列的可视化. 实现的比较好的hashCode
都应该像这样, 在整个int
的空间里,较为均匀的分布.
接下来添加几个比较一般的hashCode
实现.这里还沿用 的通项公式,但是把常数从0x61c88647
加上1000000,10000000,100000000
, 并列4个画在一起对比下.

这张图, 应该能够表现出,对于这个 通项公式来说, 这个值,确实是能够让生成的数列更加均匀.
0x61c88647是怎么求出来的?
这个值的求法要使用一个叫做三距离定理
的工具.

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

最后可以得到的结论是, 当 或
当这个数值扩大 倍
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个数列跑了一阵子以后的样子.
参考资料
《三距离定理及其连分数表示》: 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