ThreadLocal

101 阅读4分钟

ThreadLocal是什么?

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

图像.jpeg 它 实现每一个线程都有自己专属的本地变量副本,主要解决了让每个线程绑定自己的值,通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。

常用的api

图像.jpeg

Thread,ThreadLocal,ThreadLocalMap 关系

图像.jpeg

图像.jpeg 图像.jpeg threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象。 当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放。

小总结

ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以ThreadLocal为Key),不过是经过了两层包装的ThreadLocal对象:

JVM内部维护了一个线程版的Map<Thread,T>(通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中),每个线程要用到这个T的时候,用当前的线程去Map里面获取,通过这样让每个线程都拥有了自己独立的变量, 人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量。

ThreadLocal内存泄漏

什么是内存泄漏?

不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

ThreadLocal如何避免内存泄漏

图像.jpeg ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以它为Key),不过是经过了两层包装的ThreadLocal对象:

(1)第一层包装是使用 WeakReference<ThreadLocal<?>> 将ThreadLocal对象变成一个弱引用的对象;

(2)第二层包装是定义了一个专门的类 Entry 来扩展 WeakReference<ThreadLocal<?>>:

public void function01()
{
    ThreadLocal tl = new ThreadLocal<Integer>();    //line1
    tl.set(2021);                                   //line2
    tl.get();                                       //line3
}

line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象,line2调用set()方法后新建一个Entry,通过源码可知Entry对象里的k是弱引用指向这个对象。

当function01方法执行完毕后,栈帧销毁强引用 tl 也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象,若这个key引用是强引用,就会导致key指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏; 若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为null的雷)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null。

图像.jpeg

但是当我们为threadLocal变量赋值,实际上就是当前的Entry(threadLocal实例为key,值为value)往这个threadLocalMap中存放。Entry中的key是弱引用,当threadLocal外部强引用被置为null(tl=null),那么系统 GC 的时候,根据可达性分析,这个threadLocal实例就没有任何一条链路能够引用到它,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

2当然,如果当前thread运行结束,threadLocal,threadLocalMap,Entry没有引用链可达,在垃圾回收的时候都会被系统进行回收。

3 但在实际使用中我们有时候会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们小心。

总结:在不再使用该ThreadLocal对象时需手动调用remove方法进行回收,避免内存泄漏。
图像.jpeg