背景
- ThreadLocal是怎么保证取到最新值的?比如,一个Integer类型的值,假如默认是0。现在,先执行一次set操作,值为1;然后在执行一次set操作,值为2。这个时候执行get操作,发现拿到的值是2。
过程
-
散列算法运作过程
-
理解ThreadLocal中散列算法是怎么工作的演示例子
-
结果
-
结论
当执行第17次的时候,又从头开始循环了。按照这样完美的散列去分析,也会出现hash碰撞吗?如果entry[ ]不扩容,则必然会有hash碰撞。如果扩容,可能会存在hash碰撞,但是概率小。
-
-
ThreadLocal#set方法源码
假如我们设置的默认值是0,而这个时候,没有执行任何方法。
假如,tab代表Entry[],每执行一次set操作,自增1。tab默认长度是16,size是0。num代表ThreadLocal实例。散列值顺序是 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 。
当加载含ThreadLocal实例的类,并初始化这个类的时候,ThreadLocal实例的threadLocalHashCode属性被赋值成功,此属性是final修饰的,因此不可修改,后续需要的时候,直接使用即可。假如,7 = threadLocalHashCode & ( 16 -1) 。
当第一次执行set操作的时候,那么tab就需要创建一个entry实例,假如为entry01,key是num实例的地址,value是1。tab[7] = entry01(num_addr, 1),tab的长度是16,size是1。
当第二次执行set操作的时候,map不为null,此时tab[7] = entry01(num_addr, 2),tab的长度还是16,size还是1。
目前tab中布局就是:tab[7] = entry01(num_addr, 2) -
get方法源码
这个时候,执行一次get操作,根据hash算法,那么这个Entry entry = tab[7],获取到这个entry01这个实例。
-
疑问
key(ThreadLocal)是一个弱引用。如果程序没有主动置null,那么下次GC的时候就一定可以被回收掉吗?
不是。想象一下,线程执行是一段时间,如果完成对ThreadLocal的使用,但是没有置null,那么下次GC的时候,ThreadLocal会被GC掉。如果线程执行的这一段时间中,后续代码逻辑有使用到ThreadLocal实例,那在执行后续代码逻辑之前,发生GC的时候,无法GC掉ThreadLocal实例。只有当线程没有对ThreadLocal实例进行使用了,但是线程没有退出,那么下次GC的时候,ThreadLocal将会被回收,但是值v是不会被GC的,因为线程还在进行中。
为什么要弱引用ThreadLocal?
这是一个保护措施。假如一个线程的ThreadLocalMap中包含了100w个Entry实例,那么相应就有100w个ThreadLocal实例和100w个v。如果线程在运行中,但是都已经完成了对ThreadLocal的使用,那么下次GC的时候,一定可以把ThreadLocal全部GC掉。如果不是弱引用,那么这100w个ThreadLocal实例将一直等到线程结束后,才能回收。
小结
-
对应关系
一个Thread只有一个ThreadLocalMap
一个ThreadLocalMap对应多个ThreadLocal
一个ThreadLocal实例对应一个Entry实例
一个ThreadLocal实例只有唯一一个threadLocalHashCode
-
每次设置值的时候
如果没有Entry实例,则创建一个新的Entry实例
如果有Entry实例,则在已有的Entry实例上更新值即可
-
每次创建新的Entry,则一定是ThreadLocal实例第一次执行set方法,或者第一次执行get方法。
-
当加载含ThreadLocal实例的类,并初始化这个类的时候,ThreadLocal实例的threadLocalHashCode属性值被赋值成功,此属性是final修饰的,因此不可修改,后续需要的时候,直接使用即可。
-
理解对应关系,理解threadLocalHashCode唯一,才能明白为什么能够取到最新值。