ThreadLocal解读

ThreadLocal源码追踪

ThreadLocal-------ThreadLocalMap----------Entry--------WeakReference<ThreadLocal<?>>--------Entry[]-------构造函数参数ThreadLocla为k,Object对象为value------value
Thread—>维护了一个ThreadLocalMap
Entry就是一个构造函数有两个参数的对象,一个是ThraedLocal作为弱引用,实际数据为另一个参数
ThreadLocalMap的负载因子为2/3,超过阈值便进行扩容

get()

每一个Thread都会维护一个ThreadLocalMap,在Entry中作为一个属性
get(): 去我们ThreadLocalMap中的Entry[]找是否有对应的entry,是通过threadLocalHashCode和Entry数组的长度减一作为下标找到Entry 也就是里面的threadlocal值,就根据ThreadLocal对象放在ThreadLocalMap中的Entry数组中去找对应的Entry,如果为空返回null值

set(T value)

根据当前线程获取ThreadLocalMap对象。
如果ThreadLocalMap对象为空,就new一个出来。这个过程会创建一个Entry数组,初始值为16,将threadLocalHashCode和Entry数组的长度减一作为下标,在这里创建Entry对象。
如果这个对象不为空,就调用ThreadLocalMap的set方法,传入ThreadLocal对象,传进来的value作为参数,将threadLocalHashCode和Entry数组的长度减一进行&运算,计算出来的下标作为它在Entry数组中的下标。
如果这个下标位置有元素了,就获取下标位置的entry对象,通过这个对象就可以找到ThreadLocal对象,如果这个ThreadLocal对象和我们当前的线程对象相同,就将我们的值设置在里面。但如果为null,调用expungeStaleEntry删除旧元素,也就是将value值设置为null,再将我们的值设置进去。不一样并且不为null那么就去它的下一位看是否有元素,有就继续找,到最后一位置又从0号位置开始找。
那个value是保存的一个副本

HASH_INCREMENT

它的使用地方是在getEntry[]方法,计算线程在Entry数组中的位置
int i = key.threadLocalHashCode & (table.length - 1);
threadLocalHashCode是一个通过AutomicInteger调用getAndAdd方法加上HASH_INCREMENT

private final int threadLocalHashCode = nextHashCode();

/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();

/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
* 连续生成的哈希代码之间的差异——将隐式顺序线程本地ID转换为近似最优的乘法哈希值,以实现两个大小表的幂。
*/
private static final int HASH_INCREMENT = 0x61c88647;

/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

Hash冲突怎么解决

和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

使用CAS,每次增加固定的值,所以采用的是线性探测法
在这里插入图片描述
解决HasH冲突
在这里插入图片描述
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。
所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

ThreadLocal内存泄漏问题及解决办法

ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
解决办法
1.使用完后记得remove
2.ThreadLocal自己提供了这个问题的解决方案。
每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。
3.使用static修饰ThreadLocal
还可以使用 static 修饰ThreadLocal,保证ThreadLocal为强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
原因:根据可达性算法分析,类静态属性引用的对象可作为GC Roots根节点,即保证了ThreadLocal为不可回收对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值