ThreadLocal源码问题小总结

1、对ThreadLocal的理解
Threadlocal对象可以提供线程局部变量,也就是说每一个线程都拥有一份自己的变量副本多个线程之间互不干扰。一般会重写initialValue()方法来赋值。
就是每个线程第一次访问get的时候会给线程赋值,就是它使用重写initicalValue()方法分配数据。
2、ThreadLocal的原理
每个线程对应的Threadlocal对象内部都有一个Threadlocals字段,这个字段会指向堆中 的ThreadLocalMap。这个Map中存储的是当前线程与其他Threadlocal对象关联的数据。每个线程都会又一个map。线程访问某个ThreadLocal对象的get方法的时候会检测当前线程的map内部是否有key为这个ThreadLocal对象的Entry数据。如果没有的话这个ThreadLocal的initialValue()方法会创建一个Entry然后存放在ThreadLocalMap里面。

3、以ThreadLocal对象为key放到ThreadLocalMap里使用的hash是从Object中继承的hashcode()方法吗?
不是,而是使用一个黄金分割数来分配hash值,映射到散列表中是比较均匀的。例如table[0]、table[4]、table[8]、table[12]

4、ThreadLocal为什么没用jdk中的hashmap而是自己定义了一个map?
1、自己定义的map可以把key限定为特有类型,也就是threadlocal类型,而且这个key是弱引用,而hashmap中的key是强引用。弱引用是不影响对象被回收的。
2、ThreadLocalMap中写数据和查数据过程中有清理过期数据的功能,能够将过期数据给清理掉。一定程度上解决了内存泄漏的问题
5、每个线程的ThreadLocalMap是什么时间创建的?
是延迟初始化的,当第一次调用get或者set方法的时候,会检测当前线程是否已经绑定了ThreadLocalMap。没有的话就会先创建。
6、map中散列表初始化长度是多少?
是16,长度必须是2的次方数。至于为啥,和hashmap的hash寻址是一样的。
7、扩容的阈值是多少呢,在初始化的情况下。达到阈值后一定会扩容吗?
阈值是当前Entry数组的三分之二,达到扩容阈值后先调用rehash()方法,会扫描整个散列表,会把过期的数据给清理掉,重新整理这个散列表。如果整理完以后仍然达到了扩容阈值的3/4的话,才会真正的扩容。

8、扩容算法是什么样的?
先创建一个新的数组,长度是原来数组长度的两倍,然后遍历老的数组元素,将其中的数据重新按照hash算法放入到新的数组里面,然后再计算一下下一次触发扩容的阈值。然后更新散列表的引用,新数组就是扩容后的数组了。

9、ThreadLocalMap中的get方法逻辑
get时候传进来的是一个ThreadLocal对象,然后根据hash算法(该Threadlocal的hash值&(table.length-1))得到一个Index,如果这个下标对应的数据不是要查找的数据,说明在写数据的过程发生了hash冲突,Threadlocalmap并没有Next字段,所以它不能像hashmap一样用链表的方法去解决hash冲突。它使用的策略是发生hash冲突后会线性的向后寻找一个合适的位置去写。所以在get操作的时候如果第一次没命中就要继续像后查找,直到查找成功或者返回null。
10、如果第一次没命中数据,继续向后寻找的过程中要是遇到过期数据应该会怎么处理?
首先说一下什么是过期数据,ThreadLocalMap中的Entry有两个字段,一个是key,指向的是ThreadLocal这个对象;另一个是value,是线程保留的变量。key是一个弱引用。当下一次GC时候,key指向的就是一个Null。也就是使用get方法会get到Null值。在get方法向后迭代的过程中会触发一次”探测式”过期数据的回收逻辑。就是从当前桶位开始向后迭代,将碰到Key==null的数据的Entry设置为null,一直迭代到slot=null为止。
如果碰到了正常数据,会根据key重新计算出一个Index,看这个index是否等于当前位置。如果等于当前位置就说明没发生hash冲突,不等于就发生了冲突。由于当前正在执行清理过期数据的逻辑,当前桶位前面可能有过期数据被清理掉,所以这个正常数据需要找到一个其他的位置去放(因为前面可能清理了几个过期数据,有slot位置被腾出来了,所以这里需要重新rehash一下这个数据)。
11、说一下set方法的流程。
首先也是根据Key去找一下下标的slot,如果这个slot是null说明是新添加逻辑,如果不是为null情况比较复杂,可分为两种情况:第一种是添加新数据逻辑发生了hash冲突,这个时候就要线性后移找到一个合适的slot来存放数据;第二种情况就是更新,当前过程中查找到key与set的key是一致的,就会替换掉这个Entry中的value.,还有就是在线性查找的时候遇到过期数据了就要执行替换过期数据的逻辑


版权声明:本文为weixin_41306220原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。