很常见的关于ThreadLocal的面试题的问法:
1.说说你对ThreadLocal的理解。
2.ThreadLocal 是什么?有哪 些使用场景?什么是线程局部变量?
3.ThreadLocal内存泄漏分析与解决方案。
ps:想理解好ThreadLocal,必须先得理解好JVM的内存模型
多个线程共同操作一个共享变量,一定会引发并发问题,那么解决的方法就是对代码进行同步,比如synchronized关键字,但是ThreadLocal换了一种思路:让每个线程都拥有共享变量的副本,这样就不会引发多线程并发问题了。
ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个 ThreadLocalMap 对象,简单说 ThreadLocal 就是一种以空间换时间的做法, 每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方 式,避免资源在多线程间共享。
使用方法很简单:
T initialValue(),set(T value) ,T get() ,remove() 是比较常用的方法,尤其是set(T value)和get()
使用场景:跨方法的参数传递,例如:数据库连接Conn,平时我们要保证事务,并没有看到service或者dao的方法参数中有conn,其实就是通过ThreadLocal来传递的。还有skywalking中的traceId也是用ThreadLocal来实现的。
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共 享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小 心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任 何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风 险。
ThreadLocal内存泄漏分析
在ThreadLocal类中有一个静态内部类ThreadLocalMap,这个Map就是用来存储线程局部变量数据的,底层是一个Entry的数组,注意这个Entry的键 是一个弱引用,而且键类型是ThreadLocal的类型
这里又涉及到一道重要的面试题:
Java 中都有哪些引用类型? 强软弱虚
1 强引用:发生 gc 的时候不会被回收。
2 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
3 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
4 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,
用PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
这个ThreadLocalMap在Thread类中用到了,是Thread的一个成员变量
虚拟机栈中的栈帧会不断的出栈,而且当一个任务运行结束后,虚拟机栈会销毁, 那么下图中的引用就没有了
ThreadLocal对象就剩下一个虚引用引用着了,那么gc之后 ThreadLocal对象就没有了,
只剩下这么多了,大家都知道线程池中的线程是生命周期很长的,那这部分永远都占用着内存空间,就会导致内存泄漏。
所以线程池和ThreadLocal结合使用的时候一定要注意。
总结:
ThreadLocal造成内存泄漏的原因?
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所 以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会 被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现key 为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个 时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在 调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法。
ThreadLocal内存泄漏解决方案?
1.每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
2.将ThreadLocal变量尽可能定义成static final 类型的,避免频繁创建ThreadLocal实例。这样可以保证程序中一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问Entry中的Value值。
补充:下面是弱引用的例子
package cn.tulingxueyuan.xiaoshanshan.base.threadlocal;
import java.lang.ref.WeakReference;
public class Yinyong {
public static void main(String[] args) {
User user = new User() ;
// User user1 = user;
WeakReference<User> user1 = new WeakReference<>(user);
System.out.println(user);
user =null ;
System.gc();
System.out.println("gc 完成 ...");
// System.out.println(user1);
System.out.println(user1.get());
}
static class User{
String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
运行结果: