ThreadLocal总结


1. 面试的问法

  • 知道ThreadLocal吗?
  • 说说你对ThreadLocal的理解
  • 也有面试官会慢慢引导到这个话题上,比如提问“在多线程环境下,如何防止自己的变量被其它线程篡改”,将主动权交给你自己,剩下的靠自己发挥。

2. ThreadLocal的作用

本质上,ThreadLocal是通过空间换时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程都会操作各自的副本, 从而完全规避了多线程的并发问题。

3. 基本使用

ThreadLocal通常的使用方法是:将它声明为一个类的静态变量,相当于是一个全局变量。每个线程访问它时都会有自己的副本。

public class MyTest3 {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();

// 在第一次调用get时,如果没有调用过set方法,那么取得的就是默认值 (null)
System.out.println(threadLocal.get());

// 这个set实际上是把 (threadLocal, "Hello") 这样一个key-value的Entry放到当前线程的Map中去。
threadLocal.set("Hello");
System.out.println(threadLocal.get());

threadLocal.set("World");
System.out.println(threadLocal.get());
}
}

4. 底层原理

4.1. 为什么ThreadLocalMap的key使用的是弱引用?

为什么②使用的是弱引用?ThreadLocalMap.Entry继承了WeakReference,然后key是弱引用

如果stack上的threadLocal引用没有用了,按理说ThreadLocal对象就不应该存在,需要被回收掉。那如果②是一个强引用,那么它会始终指向这个TL对象,从而就无法被GC回收。
相反,②是弱引用,下次GC一定会回收这个key指向的TL对象。所以,使用弱引用是为了避免内存泄漏。

key被回收了,设置成了null,那value呢?
实际上TL的get/set/remove方法,本身会去检测那些key为null的entry,然后释放value对象。

threadLocalMap对象是定义在Thread类的,在线程exit时,会释放这个引用,回收这个map (Thread #exit)

4.2. 是否有内存泄露问题,怎么样避免?

如果使用不当,依然会有可能造成内存泄露。TL变量的生命周期,一般是整个线程,如果是在线程池中运行,线程没有及时释放,TL对象保存的对象又比较大,
那么会造成大对象一直存在于内存中,无法被GC线程回收掉。

解决: 程序中,当一个TL对象没有用后,应该手动调用remove方法避免内存泄漏,并且是在finally块调用它。

4.3. ThreadLocal变量的生命周期

Thread的exit方法,会给map引用设置为null。那么使用线程池的情况呢?

5. 源码阅读 和 验证

从get方法开始,set方法。引入内部类:ThreadLocalMap, Entry。

  • Thread #threadLocals (ThreadLocal.ThreadLocalMap) – 每个线程对象,里面都有一个map (TLM), map的Entry是一个key-value结构,key是:ThreadLocal对象,value是程序中设置的对象类型(String等)
  • 在第一次调用get时,如果没有调用过set方法,那么取得的就是默认值 (null)
  • threadLocal.set("Hello"); set实际上是把 (threadLocal, “Hello”) 这样一个key-value的Entry放到当前线程的ThreadLocalMap中去。
  • Thread类 和 ThreadLocal类 是通过ThreadLocal的内部类ThreadLocalMap关联在一起的
  • ThreadLocalMap #Entry 继承自WeakReference,为什么?
    避免内存泄露,但是如果代码写得不好,依然可能会泄露

6. SimpleDateFormat

SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
说明:如果是JDK8的应用,可以使用instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,
官方给出的解释:simple beautiful strong immutable thread-safe。

  • Positive example 1:

    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
    public String getFormat(Date date){
    SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);
    return sdf.format(date);
    }
  • Positive example 2:

    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public void getFormat(){
    synchronized (sdf){
    sdf.format(new Date());
    ….;
    }
  • Positive example 3:

    private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
    return new SimpleDateFormat("yyyy-MM-dd");
    }
    };

7. 参考

  1. Java面试必问,ThreadLocal终极篇 - 简书

文章作者: IT神助攻
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 IT神助攻 !
  目录