ThreadLocal源码分析-黄金分割数的使用

最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量。问题是解决了,但是后来发现对ThreadLocal的理解不够深入,于是顺便把它的源码阅读理解了一遍。在谈到ThreadLocal之前先买个关子,先谈谈黄金分割数。本文在阅读ThreadLocal源码的时候是使用JDK8(1.8.0_181)。

黄金分割数与斐波那契数列

首先复习一下斐波那契数列,下面的推导过程来自某搜索引擎的wiki:

斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...

通项公式:假设F(n)为该数列的第n项(n ∈ N*),那么这句话可以写成如下形式:F(n) = F(n-1) + F(n-2)。

有趣的是,这样一个完全是自然数的数列,通项公式却是用无理数来表达的。而且当n趋向于无穷大时,前一项与后一项的比值越来越逼近0.618(或者说后一项与前一项的比值小数部分越来越逼近0.618),而这个值0.618就被称为黄金分割数。证明过程如下:

ThreadLocal源码分析-黄金分割数的使用

黄金分割数的准确值为(根号5 - 1)/2,约等于0.618。

黄金分割数的应用

黄金分割数被广泛使用在美术、摄影等艺术领域,因为它具有严格的比例性、艺术性、和谐性,蕴藏着丰富的美学价值,能够激发人的美感。当然,这些不是本文研究的方向,我们先尝试求出无符号整型和带符号整型的黄金分割数的具体值:

public static void main(String[] args) throws Exception { //黄金分割数 * 2的32次方 = 2654435769 - 这个是无符号32位整数的黄金分割数对应的那个值 long c = (long) ((1L << 32) * (Math.sqrt(5) - 1) / 2); System.out.println(c); //强制转换为带符号为的32位整型,值为-1640531527 int i = (int) c; System.out.println(i); }

通过一个线段图理解一下:

ThreadLocal源码分析-黄金分割数的使用

也就是2654435769为32位无符号整数的黄金分割值,而-1640531527就是32位带符号整数的黄金分割值。而ThreadLocal中的哈希魔数正是1640531527(十六进制为0x61c88647)。为什么要使用0x61c88647作为哈希魔数?这里提前说一下ThreadLocal在ThreadLocalMap(ThreadLocal在ThreadLocalMap以Key的形式存在)中的哈希求Key下标的规则:

哈希算法:keyIndex = ((i + 1) * HASH_INCREMENT) & (length - 1)

其中,i为ThreadLocal实例的个数,这里的HASH_INCREMENT就是哈希魔数0x61c88647,length为ThreadLocalMap中可容纳的Entry(K-V结构)的个数(或者称为容量)。在ThreadLocal中的内部类ThreadLocalMap的初始化容量为16,扩容后总是2的幂次方,因此我们可以写个Demo模拟整个哈希的过程:

public class Main { private static final int HASH_INCREMENT = 0x61c88647; public static void main(String[] args) throws Exception { hashCode(4); hashCode(16); hashCode(32); } private static void hashCode(int capacity) throws Exception { int keyIndex; for (int i = 0; i < capacity; i++) { keyIndex = ((i + 1) * HASH_INCREMENT) & (capacity - 1); System.out.print(keyIndex); System.out.print(" "); } System.out.println(); } }

上面的例子中,我们分别模拟了ThreadLocalMap容量为4,16,32的情况下,不触发扩容,并且分别"放入"4,16,32个元素到容器中,输出结果如下:

3 2 1 0 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0

每组的元素经过散列算法后恰好填充满了整个容器,也就是实现了完美散列。实际上,这个并不是偶然,其实整个哈希算法可以转换为多项式证明:证明(x - y) * HASH_INCREMENT != 2^n * (n m),在x != y,n != m,HASH_INCREMENT为奇数的情况下恒成立,具体证明可以自行完成。HASH_INCREMENT赋值为0x61c88647的API文档注释如下:

连续生成的哈希码之间的差异(增量值),将隐式顺序线程本地id转换为几乎最佳分布的乘法哈希值,这些不同的哈希值最终生成一个2的幂次方的哈希表。

ThreadLocal是什么

下面引用ThreadLocal的API注释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)

稍微翻译一下:ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywdgj.html