是什么
ThreadLocal提供线程局部变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。ThreadLocal实例通常是类中的私有静态字段。$color{#FF0000}{ThreadLocal 并不解决线程间共享数据的问题。}$
使用场景
ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景。
场景一:代替参数的显式传递
当我们在写API接口的时候,通常Controller层会接受来自前端的入参,当这个接口功能比较复杂的时候,可能我们调用的Service层内部还调用了 很多其他的很多方法,通常情况下,我们会在每个调用的方法上加上需要传递的参数。
但是如果我们将参数存入ThreadLocal中,那么就不用显式的传递参数了,而是只需要ThreadLocal中获取即可。
这个场景其实使用的比较少,一方面显式传参比较容易理解,另一方面我们可以将多个参数封装为对象去传递。
场景二:全局存储用户信息
在拦截器中获取到用户信息然后存入ThreadLocal,当前线程在任何地方如果需要拿到用户信息都可以使用ThreadLocal的get()方法 (异步程序中ThreadLocal是不可靠的)。
场景三:解决线程安全问题
ThreadLocal在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。
Thread,ThreadLocal,ThreadLocalMap,Entry 关系
ThreadLocal连接了Thread和ThreadLocalMap (Thread.java里面有一个变量),ThreadLocalMap 是ThreadLocal的静态内部类,ThreadLocalMap又有一个静态内部类Entry。Entry的key为ThreadLocal的弱引用,v为value。
ThreadLocal内存泄露
内存泄漏原因
1. 当我们为threadLocal变量赋值,通过Thread,ThreadLocal,ThreadLocalMap,Entry 关系知道,实际上是存放在Entry中的,如果线程使用完ThreadLocal后不进行remove(),那么Entry中的key依然有它的引用(是弱引用,就是防止用户忘了remove(),这算是防止内存泄漏的一种保护措施),这将有可能导致内存泄漏。而且在线程复用(线程池)的情况下忘记remove()还有可能导致数据污染出现功能错误。
2. 由于Entry中的key是弱引用,(tl=null),那么系统 GC 的时候,根据可达性分析,这个threadLocal实例就没有任何一条链路能够引用到它,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
解决方法
- ThreadLocalMap的Entry中key对ThreadLocal的引用为弱引用,只要系统进行GC就可以回收,避免了用户不手动调用remove()方法造成的内存泄漏。
- 都会通过expungeStaleEntry,cleanSomeSlots,replaceStaleEntry这三个方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法
阿里手册相关