ThreadLocal小结

1.ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻,任意方法中获取缓存的数据

2.ThreadLocal底层是通过ThreadLocalMap实现的,每个Thread对象中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值

相当于每个Thread,都有一张表来存储线程本地变量的地址,这个地址就是ThreadLocal,对应的值就是ThreadLocalMap中的value

3.ThreadLocal会造成内存泄漏

ThreadLocal引用关系
造成内存泄漏的原因:线程没有被kill的情况下,key为null时,value不为null。此时value访问不到,但根据可达性分析,有CurrentThreadRef->CurrentThread->ThreadLocalMap->Entry->ValueRef->Value,所以value不会被gc回收。于是就有了无法被访问但又无法被清除的value,导致内存泄漏。

什么情况下key会为null?
当ThreadLocalRef对ThreadLocal的引用结束,在触发gc时,由于key对于ThreadLocal是弱引用,所以ThreadLocal会被回收,导致key为null;或者主动将ThreadLocal置为null。

为什么Entry的key使用弱引用?
为了使ThreadLocal被合理地回收。当外部强引用ThreadLocalRef->ThreadLocal被清除,也就代表ThreadLocal不再使用了,ThreadLocal就应该会被gc回收。此时即使key引用ThreadLocal,由于是弱引用,也能正常回收ThreadLocal。

如果Entry的key使用强引用,会怎么样?
ThreadLocal的调用结束了,线程不一定会结束,CurrentThreadRef->CurrentThread->ThreadLocalMap->Key->ThreadLocal的引用会一直存在,导致ThreadLocal只能在线程结束时被回收。

这里所说的内存泄漏有两种情况:一是value的泄漏,二是ThreadLocal的泄漏。在使用强引用时,会同时导致ThreadLocal与value泄漏,因为在业务代码中,ThreadLocalRef一旦不再使用,ThreadLocal也不会被回收,并且此时value也无法在业务代码中进行访问。而使用弱引用,只会存在value泄漏的情况。

所以为了避免在这种使用弱引用的情况下造成内存泄漏(value),应如下实践:

  1. 尽量一定要先初始化:ThreadLocal.withInitial(() -> 初始化)
  2. 建议:把ThreadLocal修饰为static
  3. 强制:用完记得手动remove()

为什么线程结束value就会被回收还要进行如此管理?
一方面程序员的基本素养应该及时回收无用空间降低gc频率,另一方面在使用线程池进行线程管理时可能会复用线程,导致线程一直运行无法正常回收泄漏的value。

如果不按规范调用remove(),能够对key为null的value进行回收的条件就只有:

  • 在你的线程中有两个或以上ThreadLocal时:
    1.调用getEntry()发生哈希冲突;
    2.调用set()发生哈希冲突或扩容;
    3.调用remove()。

只有在当前线程存在多个ThreadLocal,才能对已经泄漏的value进行回收。因为如果只有一个ThreadLocal,并且已经泄漏,那说明这个ThreadLocal已经是null了,上述方法都需要ThreadLocal作为key参数,还调用个屁。详见:ThreadLocalMap内存泄漏“自动回收的触发场景”分析

4.Thread、ThreadLocal、ThreadLocalMap的关系

Thread、ThreadLocal、ThreadLocalMap的关系

  1. 每个Thread线程内部都定义有一个ThreadLocal.ThreadLocalMap类型的threadLocals变量,用于存放线程本地变量(key为ThreadLocal对象,value为要存储的数据),这样,线程之间的ThreadLocalMap互不干扰。threadLocals变量持有的ThreadLocalMap在ThreadLocal调用set或者get方法时才会初始化。
  2. ThreadLocal类中定义了一个内部类ThreadLocalMap,ThreadLocalMap是真正存放数据的容器,实际上它的底层就是一张哈希表。
  3. ThreadLocal还提供相关方法,负责向当前线程的ThreadLocalMap变量获取和设置线程的变量值,相当于一个工具类
  4. 当在某个线程的方法中使用ThreadLocal设置值的时候,就会将该ThreadLocal对象添加到该线程内部的ThreadLocalMap中,其中键就是该ThreadLocal对象,值可以是任意类型任意值。当在某个线程的方法中使用ThreadLocal获取值的时候,会以该ThreadLocal对象为键,在该线程的ThreadLocalMap中获取对应的值。

5.ThreadLocal的应用场景

连接管理:一个线程持有一个链接,该链接对象被线程中不同方法进行调用,但不同线程不共享同一个链接。

参考:
https://blog.csdn.net/weixin_46956020/article/details/134223544
https://blog.csdn.net/qq_42200163/article/details/126474895
https://blog.csdn.net/super_scan/article/details/129742823

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值