• 面试官:今天要不来聊聊ThreadLocal吧?


    面试官今天要不来聊聊ThreadLocal吧?

    候选者:我个人对ThreadLocal理解就是

    候选者:它提供了线程的局部变量,每个线程都可以通过set/get来对这个局部变量进行操作

    候选者:不会和其他线程的局部变量进行冲突,实现了线程的数据隔离

    e6e8d67dfabd4e6faead1c37ada929aa.png 

    面试官你在工作中有用到过ThreadLocal吗?

    候选者:这块是真不多,不过还是有一处的。就是我们项目有个的DateUtils工具类

    候选者:这个工具类主要是对时间进行格式化

    候选者:格式化/转化的实现是用的SimpleDateFormat

    候选者:但众所周知SimpleDateFormat不是线程安全的,所以我们就用ThreadLocal来让每个线程装载着自己的SimpleDateFormat对象

    候选者:以达到在格式化时间时,线程安全的目的

    候选者:在方法上创建SimpleDateFormat对象也没问题,但每调用一次就创建一次有点不优雅

    候选者:在工作中ThreadLocal的应用场景确实不多,但要不我给你讲讲Spring是怎么用的?

    面试官:好吧,你讲讲呗

    候选者:Spring提供了事务相关的操作,而我们知道事务是得保证一组操作同时成功或失败的

    候选者:这意味着我们一次事务的所有操作需要在同一个数据库连接上

    候选者:但是在我们日常写代码的时候是不需要关注这点的

    候选者:Spring就是用的ThreadLocal来实现,ThreadLocal存储的类型是一个Map

    候选者:Map中的key 是DataSource,value 是Connection(为了应对多数据源的情况,所以是一个Map)

    候选者:用了ThreadLocal保证了同一个线程获取一个Connection对象,从而保证一次事务的所有操作需要在同一个数据库连接上

    57c25495774b49b3a863c96c137a0721.png 

    面试官:了解

    面试官你知道ThreadLocal内存泄露这个知识点吗?

    候选者:怎么都喜欢问这个…

    候选者:了解的,要不我先来讲讲ThreadLocal的原理?

    面试官请开始你的表演吧

    候选者:ThreadLocal是一个壳子,真正的存储结构是ThreadLocal里有ThreadLocalMap这么个内部类

    候选者:而有趣的是,ThreadLocalMap的引用是在Thread上定义的

    候选者:ThreadLocal本身并不存储值,它只是作为key来让线程从ThreadLocalMap获取value

    候选者:所以,得出的结论就是ThreadLocalMap该结构本身就在Thread下定义,而ThreadLocal只是作为key,存储set到ThreadLocalMap的变量当然是线程私有的咯

    aa1d645c54d04168ba76752ee2723bbe.png 

    面试官:那我想问下,我可以在ThreadLocal下定义Map,key是Thread,value是set进去的值吗?

    面试官:就是说,为啥我要把ThreadLocal做为key,而不是Thread做为key?这样不是更清晰吗?

    候选者:嗯,我明白你的意思。

    候选者:理论上是可以,但没那么优雅。

    候选者:你提出的做法实际上就是所有的线程都访问ThreadLocal的Map,而key是当前线程

    候选者:但这有点小问题,一个线程是可以拥有多个私有变量的嘛,那key如果是当前线程的话,意味着还点做点「手脚」来唯一标识set进去的value

    候选者:假设上一步解决了,还有个问题就是;并发量足够大时,意味着所有的线程都去操作同一个Map,Map体积有可能会膨胀,导致访问性能的下降

    候选者:这个Map维护着所有的线程的私有变量,意味着你不知道什么时候可以「销毁」

    候选者:现在JDK实现的结构就不一样了。

    候选者:线程需要多个私有变量,那有多个ThreadLocal对象足以,对应的Map体积不会太大

    候选者:只要线程销毁了,ThreadLocalMap也会被销毁

    ed298f214abc4af1b1be43237b54ad96.png 

    面试官:嗯,了解。

    面试官回到ThreadLocal内存泄露上吧,谈谈你对这个的理解呗

    候选者:ThreadLocal内存泄露其实发生的概率非常非常低,我也不知道为什么这么喜欢问。

    候选者:回到原理上,我们知道Thread在创建的时候,会有栈引用指向Thread对象,Thread对象内部维护了ThreadLocalMap引用

    候选者:而ThreadLocalMap的Key是ThreadLocal,value是传入的Object

    候选者:ThreadLocal对象会被对应的栈引用关联,ThreadLocalMap的key也指向着ThreadLocal

    候选者:ThreadLocalRef && ThreadLocalMap Entry key ->ThreadLocal

    候选者:ThreadRef->Thread->ThreadLoalMap-> Entry value-> Object

    候选者:网上大多分析的是ThreadLocalMap的key是弱引用指向ThreadLocal

    面试官:嗯…要不顺便讲讲Java的4种引用吧

    候选者:强引用是最常见的,只要把一个对象赋给一个引用变量,这个引用变量就是一个强引用

    候选者:强引用:只要对象没有被置null,在GC时就不会被回收

    候选者:软引用相对弱化了一些,需要继承 SoftReference实现

    候选者:软引用:如果内存充足,只有软引用指向的对象不会被回收。如果内存不足了,只有软引用指向的对象就会被回收

    候选者:弱引用又更弱了一些,需要继承WeakReference实现

    候选者:弱引用:只要发生GC,只有弱引用指向的对象就会被回收

    候选者:最后就是虚引用,需要继承PhantomReference实现

    候选者:虚引用的主要作用是:跟踪对象垃圾回收的状态,当回收时通过引用队列做些「通知类」的工作

    1f01129472fc43849b75d0b9064aa434.png 

    候选者:了解了这几种引用之后,再回过头来看ThreadLocal

    面试官:嗯..

    候选者:ThreadLocal内存泄露指的是:ThreadLocal被回收了,ThreadLocalMap Entry的key没有了指向

    候选者:但Entry仍然有ThreadRef->Thread->ThreadLoalMap-> Entry value-> Object 这条引用一直存在导致内存泄露

    面试官:嗯..

    候选者:为什么我说导致内存泄露的概率非常低呢,我觉得是这样的

    候选者:首先ThreadLocal被两种引用指向

    候选者:1):ThreadLocalRef->ThreadLocal(强引用)

    候选者:2):ThreadLocalMap Entry key ->ThreadLocal(弱引用)

    候选者:只要ThreadLocal没被回收(使用时强引用不置null),那ThreadLocalMap Entry key的指向就不会在GC时断开被回收,也没有内存泄露一说法

    候选者:通过ThreadLocal了解实现后,又知道ThreadLocalMap是依附在Thread上的,只要Thread销毁,那ThreadLocalMap也会销毁

    候选者:那非线程池环境下,也不会有长期性的内存泄露问题

    候选者:而ThreadLocal实现下还做了些”保护“措施,如果在操作ThreadLocal时,发现key为null,会将其清除掉

    候选者:所以,如果在线程池(线程复用)环境下,如果还会调用ThreadLocal的set/get/remove方法

    候选者:发现key为null会进行清除,不会有长期性的内存泄露问题

    候选者:那存在长期性内存泄露需要满足条件:ThreadLocal被回收&&线程被复用&&线程复用后不再调用ThreadLocal的set/get/remove方法

    968e09e172e24f0c9feee5684cf92600.png 

    候选者:使用ThreadLocal的最佳实践就是:用完了,手动remove掉。就像使用Lock加锁后,要记得解锁

    面试官:那我想问下,为什么要将ThreadLocalMap的key设置为弱引用呢?强引用不香吗?

    候选者:外界是通过ThreadLocal来对ThreadLocalMap进行操作的,假设外界使用ThreadLocal的对象被置null了,那ThreadLocalMap的强引用指向ThreadLocal也毫无意义啊。

    候选者:弱引用反而可以预防大多数内存泄漏的情况

    候选者:毕竟被回收后,下一次调用set/get/remove时ThreadLocal内部会清除掉

    面试官我看网上有很多人说建议把ThreadLocal修饰为static,为什么?

    候选者:ThreadLocal能实现了线程的数据隔离,不在于它自己本身,而在于Thread的ThreadLocalMap

    候选者:所以,ThreadLocal可以只初始化一次,只分配一块存储空间就足以了,没必要作为成员变量多次被初始化。

    面试官:最后想问个问题:什么叫做内存泄露?

    候选者:…..

    候选者:意思就是:你申请完内存后,你用完了但没有释放掉,你自己没法用,系统又没法回收。

    面试官:清楚了

    本文总结

    • 什么是ThreadLocal:它提供了线程的局部变量,每个线程都可以通过set/get来对这个局部变量进行操作,不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。

    • ThreadLocal实际用处(举例):Spring事务,ThreadLocal里存储Map,Key是DataSource,Value是Connection

    • ThreadLocal设计:Thread有ThreadLocalMap引用,ThreadLocal作为ThreadLocalMap的Key,set和get进去的Value则是ThreadLocalMap的value

    • ThreadLocal内存泄露:ThreadLocal被回收&&线程被复用&&线程复用后不再调用ThreadLocal的set/get/remove方法 才可能发生内存泄露(条件还是相对苛刻)

    • ThreadLocal最佳实践:用完就要remove掉

  • 相关阅读:
    【云原生之k8s】KubeSphere介绍及安装
    flutter 一键打出不同包名、应用名、版本名、签名、应用图标、版本号的安装包
    MyBatis缓存
    git关于分支
    CPT205-Computer Graphics
    mybatis-plus集成pagehelper进行分页排序和返回查询总数
    东北大学复合材料学——铝基复合材料在飞行器反动轮和方向架上的应用
    Curve 块存储应用实践 -- iSCSI
    MAC M1大数据0-1成神篇-25 hadoop高可用搭建
    Redis 常见面试题
  • 原文地址:https://blog.csdn.net/m0_72088858/article/details/126458736