threadLocal内存泄漏原理分析
在使用线程池时,可以减少线程的重复创建,实现线程复用,但是再进行复用时如果使用了threadLocal可能会出现内存泄漏问题,下面一起学习为什么会出现内存泄漏,以及如何处理这类问题。
首先用代码展示同一个线程中的不同threadLocal实例共享同一个ThreadLocalMap。
public class WeakReferenceDemo {
public static void main(String[] args) {
Thread t = Thread.currentThread();
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("value1"); // 创建Entry: WeakRef(tl) → "value"
ThreadLocal<String> t2 = new ThreadLocal<>();
t2.set("value2");
tl = null; // 没有外部强引用了
System.gc(); // 触发GC
}
}
开启debug后,观察变量t中的threadLocal,可以发现每次调用set方法,threadLocal的size都会自增1,同时在tabel中新增一个Entry对象,新增的entry对象的referent类型是ThreadLocal,value是set方法中设置的值。
在手动触发CG后,观察对应的entry,发现referent的值设置为null,而value保持不变。
如果在手动触发remove方法,就可以清除entry,这样就不会出现value无法清除的问题了
线程执行过程中可能会出现异常,为了保证remove方法一定能够被执行,需要使用try-finally块确保remove被执行
为了降低程序响应时间,通常会使用多线程,在threadLocal中经常会存一些重要的参数信息,便于后续程序的使用,但是ThreadLocal是线程隔离的,子线程无法访问父线程的ThreadLocal值,InheritableThreadLocal可以在创建子线程时传递值,但是在使用线程池时,由于线程复用,这个方法不适用,TTL可以解决线程池中上下文传递问题。 TTl工作的原理: TtlRunnable实现了Runnable接口,构造方法中传入Runnable,同时使用AtomicReference获取当前上下文的ThreadLocal,完成对原有Runnable的包装。 TtlRunnable重写了run方法,在调用原有Runnable方法前,取出之前保存的ThreadLocal, 调用replay()方法保存当前线程的上下文,同时重新设置threadLocal。finally块中执行restore() ,完成上下文的恢复。