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 }
适用场景
- 为解决线程安全性问题。通过ThreadLocal隔离临时对象;
- 为了在不同方法中传递上下文信息;
- 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的可能。