简介
Java为开发者提供的ThreadLocal方便了实现线程隔离及隐式传参场景的需求,具体介绍与避坑可以回看博文:
今天主要分析ThreadLocal的实现原理。
ThreadLocal的实现原理分析
下图展示了ThreadLocal、当前线程Thread及线程内部ThreadLocalMap在内存中的引用关系。
每个线程对象内部都有一个ThreadLocalMap对象,由于访问权限的设置只能通过ThreadLocal访问,保证操作线程私有且线程安全。
ThreadLocalMap内部是一个哈希表数据结构:
private Entry[] table;
每个哈希元素Entry持有的key是ThreadLocal实例的弱引用,value是ThreadLocal实例关联的数据对象的引用。
源码分析java.lang.ThreadLocal#set方法如何设值:
1、通过Thread.currentThread() 获取当前线程,只会操作当前线程的ThreadLocalMap,保证线程安全;
2、获取当前线程内部持有的ThreadLocalMap;
3、如果哈希map已经初始化,则直接保存java.lang.ThreadLocal.ThreadLocalMap.
Entry< WeakReference< 当前ThreadLocal实例 > ,value>;
4、如果哈希map还没初始化即第一次保存数据,则初始化当前线程的ThreadLocalMap,并设置初始值;
可见,线程内部的ThreadLocalMap是延迟初始化的。
源码分析java.lang.ThreadLocal#get方法如何获取当前线程数据:
1、获取当前线程Thread.currentThread();
2、获取当前线程的ThreadLocalMap;
3、如果当前线程的ThreadLocalMap已经初始化了,则哈希表查询,key参数为当前ThreadLocal实例;
4、如果当前线程的ThreadLocalMap还没有初始化,则创建哈希表,设置初始值;
ThreadLocalMap的实现原理分析
ThreadLocalMap是ThreadLocal的静态内部类,默认包访问级别,这样使得同一个包下的Thread才能声明:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
而且ThreadLocalMap除了一个构造函数的访问权限是包访问级别,其它方法都是私有的,这样使得ThreadLocalMap的操作限制在ThreadLocal类内。
ThreadLocalMap的哈希数组中,保存的是ThreadLocal的弱引用,是为了防止内存泄露。
实例场景分析ThreadLocal的弱引用是如何防止内存泄露的:
如果方法funA被一个线程池中的一个线程线程执行完,内存引用图如下:
方法栈帧退出,使得userInfoContext不再引用到内存ThreadLocal实例,此时内存中的ThreadLocal实例是需要被GC的,但是线程池中的线程如果是核心线程,则生命周期和JVM的生命周期一样的话,线程内部的ThreadLocalMap及内部的Entry都不会被释放,如果Entry的key是强引用,则导致ThreadLocal实例永不会被GC回收,如果Entry的key是弱引用,此时ThreadLocal实例无强引用关联,在GC时会被回收。
虽然Entry的key是弱引用,GC能回收无强引用关联的ThreadLocal实例,但是Entry中的value引用的内存实例还是不能回收的,不过此功能的实现者也考虑到了,在调用任何其它ThreadLocal实例的set、remove方法时,会回收这些Entry的弱引用key为null的value对象及Entry对象实例的清理(设置为null)。
注意:ThreadLocalMap哈希表的实现是与HashMap不同的,冲突处理使用了开放定址法。
ThreadLocal的注意事项
1、尽量使用private static final ThreadLocal<>,使用工具类封装ThreadLocal的set、get、remove方法;
目的是访问权限控制,而且保证单例性。
实例:
org.springframework.web.context.request.RequestContextHolder
2、ThreadLocal使用完务必在finally块执行remove方法;
目的是避免内存泄露。
实例:
org.springframework.web.filter.RequestContextFilter#doFilterInternal
3、ThreadLocal使用之前,根据业务场景务必重新初始化;
目的是避免信息错乱或信息丢失。因为线程池场景下,线程复用,如果不初始化,使用的是上一次设置的值,或者是隐士传参不重新设值,导致线程切换信息丢失。
实例:
org.springframework.web.filter.RequestContextFilter#doFilterInternal