ThreadLocal和ThreadLocalMap

ThreadLocal
是 “钥匙”,
ThreadLocalMap
是线程持有的 “储物柜”,每个 “钥匙”(
ThreadLocal
)对应储物柜中一个 “格子”(
Entry
),存储该线程的变量副本

ThreadLocal:以空间换时间的方式为每个线程提供一个独立的变量副本;隔离多线程下对一个有状态的对象的数据访问冲突;

ThreadLocalMap:

ThreadLocalMap
ThreadLocal
的静态内部类,其内部通过
Entry
数组存储键值对。
Entry
key
ThreadLocal
实例(弱引用),
value
是该线程中该
ThreadLocal
对应的变量副本。每个
Thread
对象,当线程首次使用
ThreadLocal
时,会初始化该
ThreadLocalMap

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value; // 线程本地变量的值
        Entry(ThreadLocal<?> k, Object v) {
            super(k); // key是ThreadLocal的弱引用
            value = v;
        }
    }
    private Entry[] table; // 存储键值对的数组
}

public class Thread {
    ThreadLocal.ThreadLocalMap threadLocals = null; // 线程持有的ThreadLocalMap
}

适用场景

  1. 解决线程安全性问题。通过ThreadLocal隔离临时对象;
  2. 为了在不同方法中传递上下文信息
  • T initialValue()
    : 给定一个初始值,并且延迟加载,直到get()才会触发初始化;
  • void set(T t)
    : 为这个线程设置一新值
  • T get()
    : 得到这个线程对应的value;
  • void remove()
    : 删除这个线程得到的值;

弱引用的设计

弱引用的特点是:当对象仅被弱引用指向时,会被 GC 回收。 ThreadLocalMap中弱引用的设计本身就是为了防止内存泄漏

  • 短生命周期线程(单次运行的线程)运行完毕后,Thread对象被回收时,即使不释放
    ThreadLocalMap
    的本地变量,也会因为弱引用的存在,使得
    ThreadLocalMap
    的Entry在下次GC时被释放掉;
  • 但是对于长生命周期的线程(线程池核心线程)就需要注意一定要适当时
    remove

内存泄露条件

什么情况下

ThreadLocalMap
会发生内存泄漏:

长生命周期线程(线程池核心线程)没有调用

ThreadLocal.remove()
移除Entry;此时如果
ThreadLocalMap
内存储大对象,在业务流程结束后,对象会驻留在内存中,虽然下次线程任务会覆盖此对象,但是在此期间,会挤占整个JVM的内存,大幅增加FullGC的可能。