JDK源码研究(四):ThreadLocal类

 

JDK源码研究(四):ThreadLocal类

ThreadLocal没有继承任何其他类(默认继承Object类),类的属性也比较少。threadLocalHashCode是该类用ThreadLocalMap中获取value时使用的散列值。具体的生成方法如下:

JDK源码研究(四):ThreadLocal类

可以看出,它是在上一个被构造出的ThreadLocal的threadLocalHashCode的基础上加上一个魔数0x61c88647的。这个魔数的选取与斐波那契散列有关,0x61c88647对应的十进制为1640531527。斐波那契散列的乘数可以用(long) ((1L << 31) * (Math.sqrt(5) - 1))可以得到2654435769,如果把这个值给转为带符号的int,则会得到-1640531527。换句话说
(1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1)) 得到的结果就是1640531527也就是0x61c88647。通过理论与实践,当我们用0x61c88647作为魔数累加为每个ThreadLocal分配各自的ID也就是threadLocalHashCode再与2的幂取模,得到的结果分布很均匀。
ThreadLocalMap使用的是线性探测法,均匀分布的好处在于很快就能探测到下一个临近的可用slot,从而保证效率。

JDK源码研究(四):ThreadLocal类

再来看构造方法,非常的简单,什么也没做。再看常用的get()方法,直接获取当前线程,然后获取当前线程维护的实例属性threadLocals,就是之前提到的ThreadLocalMap类的实例,这个实例如果为null会在get()或者set()方法中进行初始化,而不是在线程构造的时候初始化的。如果一直没用到ThreadLocal对象,线程的该属性就会一直为null。

JDK源码研究(四):ThreadLocal类

JDK源码研究(四):ThreadLocal类

JDK源码研究(四):ThreadLocal类

通过这两段代码可以看出,当线程里的threadLocals为null时候,会构造一个ThreadLocalMap实例给当前线程。并将当前ThreadLocal对象作为键,和一个null作为值放入初始化的ThreadLocalMap实例中。

从中可以看出,每个线程都维护了自己的ThreadLocalMap实例,而该ThreadLocalMap实例中维护了所有该线程用到过的ThreadLocal对象作为键的键值对。

JDK源码研究(四):ThreadLocal类

set()方法的逻辑也差不多,其实ThreadLocal最核心的代码是在其静态内部类ThreadLocalMap类中。

JDK源码研究(四):ThreadLocal类

先来看ThreadLocal用来维护键值对的Entry对象,继承了WeakReference对象。WeakReference是JAVA中的弱引用,这里Entry类将ThreadLocal这个键传入WeakReference的构造方法,作为一个弱引用对象,当线程中ThreadLocal对象的强引用消失时,下一次GC会把ThreadLocal这个键回收。网上大佬的解释是这样的:

读到这里,如果不问不答为什么是这样的定义形式,为什么要用弱引用,等于没读懂源码。
因为如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

JDK源码研究(四):ThreadLocal类

ThreadLocalMap类的属性都比较简单,从上至下分别是初始化容量、储存键值对的数组、实际大小、负载因子。

JDK源码研究(四):ThreadLocal类

再看其获取上一个索引和下一个索引的方法,可以看出ThreadLocalMap类是一种环形链表的结构,如下图:

JDK源码研究(四):ThreadLocal类

让我们来看看获取Entry的方法

JDK源码研究(四):ThreadLocal类

通过ThreadLocal这个键的散列值和表的长度取模后获取具体索引值,该索引位的Entry不为空并且键相等的话直接返回,如果不相等或者为空就需要继续查找。这里ThreadLocalMap使用了线性探测来防止哈希碰撞,所以其具体索引位不一定是在取模后得出的索引值。

JDK源码研究(四):ThreadLocal类

当Entry不为null并且不是是当前的要查找的键的时候,会顺着环形索引继续查找(线性探测),因为添加Entry的时候也会根据当前索引位被占用时,顺着环形索引寻找下一个空位。

https://www.cnblogs.com/micrari/p/6790229.html 这篇博客的大佬解释得比较详细和清楚,文中很多都是引用大佬的