面试官:今天要不来聊聊ThreadLocal吧?
候选者:我个人对ThreadLocal理解就是
候选者:它提供了线程的局部变量,每个线程都可以通过set/get来对这个局部变量进行操作
候选者:不会和其他线程的局部变量进行冲突,实现了线程的数据隔离
面试官:你在工作中有用到过ThreadLocal吗?
候选者:这块是真不多,不过还是有一处的。就是我们项目有个的DateUtils工具类
候选者:这个工具类主要是对时间进行格式化
候选者:格式化/转化的实现是用的SimpleDateFormat
候选者:但众所周知SimpleDateFormat不是线程安全的,所以我们就用ThreadLocal来让每个线程装载着自己的SimpleDateFormat对象
候选者:以达到在格式化时间时,线程安全的目的
候选者:在方法上创建SimpleDateFormat对象也没问题,但每调用一次就创建一次有点不优雅
候选者:在工作中ThreadLocal的应用场景确实不多,但要不我给你讲讲Spring是怎么用的?
面试官:好吧,你讲讲呗
候选者:Spring提供了事务相关的操作,而我们知道事务是得保证一组操作同时成功或失败的
候选者:这意味着我们一次事务的所有操作需要在同一个数据库连接上
候选者:但是在我们日常写代码的时候是不需要关注这点的
候选者:Spring就是用的ThreadLocal来实现,ThreadLocal存储的类型是一个Map
候选者:Map中的key 是DataSource,value 是Connection(为了应对多数据源的情况,所以是一个Map)
候选者:用了ThreadLocal保证了同一个线程获取一个Connection对象,从而保证一次事务的所有操作需要在同一个数据库连接上
面试官:了解
面试官:你知道ThreadLocal内存泄露这个知识点吗?
候选者:怎么都喜欢问这个…
候选者:了解的,要不我先来讲讲ThreadLocal的原理?
面试官:请开始你的表演吧
候选者:ThreadLocal是一个壳子,真正的存储结构是ThreadLocal里有ThreadLocalMap这么个内部类
候选者:而有趣的是,ThreadLocalMap的引用是在Thread上定义的
候选者:ThreadLocal本身并不存储值,它只是作为key来让线程从ThreadLocalMap获取value
候选者:所以,得出的结论就是ThreadLocalMap该结构本身就在Thread下定义,而ThreadLocal只是作为key,存储set到ThreadLocalMap的变量当然是线程私有的咯
面试官:那我想问下,我可以在ThreadLocal下定义Map,key是Thread,value是set进去的值吗?
面试官:就是说,为啥我要把ThreadLocal做为key,而不是Thread做为key?这样不是更清晰吗?
候选者:嗯,我明白你的意思。
候选者:理论上是可以,但没那么优雅。
候选者:你提出的做法实际上就是所有的线程都访问ThreadLocal的Map,而key是当前线程
候选者:但这有点小问题,一个线程是可以拥有多个私有变量的嘛,那key如果是当前线程的话,意味着还点做点「手脚」来唯一标识set进去的value
候选者:假设上一步解决了,还有个问题就是;并发量足够大时,意味着所有的线程都去操作同一个Map,Map体积有可能会膨胀,导致访问性能的下降
候选者:这个Map维护着所有的线程的私有变量,意味着你不知道什么时候可以「销毁」
候选者:现在JDK实现的结构就不一样了。
候选者:线程需要多个私有变量,那有多个ThreadLocal对象足以,对应的Map体积不会太大
候选者:只要线程销毁了,ThreadLocalMap也会被销毁
面试官:嗯,了解。
面试官:回到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实现
候选者:虚引用的主要作用是:跟踪对象垃圾回收的状态,当回收时通过引用队列做些「通知类」的工作
候选者:了解了这几种引用之后,再回过头来看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方法
候选者:使用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掉
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,
咱们下期见!答案获取方式:已赞 已评 已关~
学习更多知识与技巧,关注与私信博主(03)