打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
ThreadLocal源码分析以及面试问题

在阅读源码的时候总是会遇到对ThreadLocal的使用,是时候梳理下这个类的作用了。

个人理解

网上看到解释比如叫做线程本地变量之类,看到了但是总是感觉没有明白ThreadLocal真正是用来干嘛的,这里我做一个自己的理解,可能要啰嗦点,但尽可能的讲清楚。

ThreadLocal对象可以作为一个公共对象,每个线程都可以往里面保存一个数据,也可以从ThreadLocal对象中获取到设置的数据,但是每个线程都只能拿到自己设置的数据,当然多个ThreadLocal对象就可以存多个数据。

ThreadLocal源码分析

ThreadLocal的使用比较简单这里就不给示例了,主要有set、get、remove三个方法,见名知意他们的作用就不再赘述了,直接看set方法的源码。

set方法非常简单只有三步,首先是获取到当前线程t,然后是根据当前线程获取到一个ThreadLocalMap对象,最后把当前ThreadLocal对象最为key把要设置的值作为value保存到ThreadLocalMap中去。

第一步就不说了,看第二步是调用getMap方法获取的ThreadLocalMap对象,进入getMap方法发现最终调用的是当前线程t的threadLocals属性。

也就是说每个线程自己有个ThreadLocalMap属性,他保存着每个ThreadLocal对象对应的值,也就是说ThreadLocal对象的set方法不过是把它绑定一个值放到当前线程的一个属性中去。这样一想感觉并不是ThreadLocal保存线程的变量,而是线程了保存一系列ThreadLocal对象对应的值。画了一个图再来梳理一下他们的关系,如下图:

ThreadLocal作为一个公共的资源,每个线程都可以调用它的set方法,实际上就是在当前线程的threadLocals属性添加一个用ThreadLocal对象作为key,设置的值作为value的键值对

ThreadLocal有一个属性threadLocalHashCode,每new一个ThreadLocal对象都会使用ThreadLocal的AtomicInteger类型的静态变量通过getAndAdd方法获取到一个递增的threadLocalHashCode,所以一个系统中每个ThreadLocal对象的threadLocalHashCode属性值是不同的。

然后再简单说一下线程属性threadLocals的类型ThreadLocalMap,ThreadLocalMap看名字也和map结构相似,主要维护一个Entry数组,每个entry保存ThreadLocal对象和存储的值,而每个entry在数组中的位置由ThreadLocal对象的threadLocalHashCode决定。

值得注意的是由于Entry不是链表结构,所以有可能出现hash冲突的问题,所以在根据threadLocalHashCode计算出Entry数组数组的索引并找到对应的entry后会验证entry保存的ThreadLocal是不是当前的threadLocal或者为null,如果不是则索引加一继续判断,达到最大则再从0开始再次判断,所以这个方法在冲突很大的情况下效率并不高。当添加到数组长度的三分之二时会扩容,所以不会陷入死循环。

理解了数据存储原理,get方法过程就是根据调用get的ThreadLocal对象去当前线程的threadLocals中找到对应的entry并判断保存的ThreadLocal对象是否是当前对象,是则返还entry保存的值,比较简单就不赘述了。

面试问题

ThreadLocal最常见的问题就是内存泄漏的问题,首先分析下内存泄漏的原因,在上面说到的Entry继承了“WeakReference<ThreadLocal<?>>”也就是弱引用,弱引用的作用是当GC的时候如果发现对象只具有弱引用,那么就会回收这个对象。

比如线程池的一个线程new了一个ThreadLocal叫做tl并设置了值,那么在线程的threadLocals中就会有一个entry对象,当线程被回收进线程池重新利用时,就不会有任何的强引用引用tl,下次回收的时候tl就会被回收,也就是entry的key是null。

但是因为线程一直会存活,所以threadLocals也会一直存在,那么这个entry也会一直存在,对应的value也会一直存在,但是key已经是null了,这样没用的value就不会被回收,这样的对象多起来就会出现内存泄漏了。

那么如何解决呢?ThreadLocal提供了remove方法就是做这个事情的,它会移除对应的entry持有的引用,以及value,同时会把entry从数组中移除。实际上在set方法发现entry的key为null的时候也会覆盖,同时get在发现key为null的时候也会清除这个数据,做的还是比较安全的。

第二个问题是为什么entry中要用弱引用?这个实际上和上面的原因一样,如果不用弱引用,那么只要线程不回收,而在使用了ThreadLocal的set方法后没有remove,那么对应entry才是真正的无法回收了。

总结

ThreadLocal表面上像是一个公共的容器,每个线程都可以往里面保存一个数据,并且不用担心其他线程干扰。实则还是当前线程自己保存数据,只不过是利用不同的ThreadLocal对象来从线程中获取对应的数据。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ThreadLocal 工作原理、部分源码分析
ThreadLocal的源码分析
一文搞懂 ThreadLocal 原理
ThreadLocal源码分析
深入理解 ThreadLocal
深入分析ThreadLocal
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服