一. ThreadLocal是什么
ThreadLocal 可以理解为线程本地变量,每个线程拥有本地变量的副本,各个线程之间的变量互不干扰,主要用于保证多线程环境下变量的安全。
也就是对于同一个 ThreadLocal,每个线程通过 get、set、remove 接口操作只会影响自身线程的数据,不会干扰其他线程中的数据。
二. ThreadLocal的数据结构
ThreadLocal 的数据结构如下图所示:
可以看出,每个线程都拥有自己的一个 ThreadLocalMap,它是 ThreadLocal 中的内部类,使用数组来存储数据,数组存放的元素类型为 Entry。Thread 类中包含的成员变量 threadLocals 就是 ThreadLocalMap 类型的,类图如下所示:
可以看出,ThreadLocalMap 没有实现 Map 接口,是用独立的方式实现了 Map 的功能,其内部的 Entry 也是独立实现的,代码如下:
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
Entry 继承自 WeakReference(弱引用,生命周期只能存活到下次GC前),但只有 Key 是弱引用类型的,Value 并非弱引用。
可以简单理解成 ThreadLocalMap 的 Key 是 ThreadLocal 变量,Value 是线程本地的数据。
三. ThreadLocal核心方法解析
ThreadLocal 类提供如下几个核心方法:
//用于获取当前线程的副本变量值
public T get()
//用于保存当前线程的副本变量值
public void set(T value)
//移除当前线程的副本变量值
public void remove()
get() 方法实现:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根据当前ThreadLocal对象应用获取Entry,存在则直接返回value值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
// 如果threadLocals为空,初始化当前线程threadLocals的变量
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// threadLocals存在,设置初始值;不存在,初始化threadLocals变量
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
set() 方法实现:
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程自身的threadLocals变量
ThreadLocalMap map = getMap(t);
if (map != null)
// map不为空,则设置
map.set(this, value);
else
// map为空,说明第一次调用,初始化线程的threadLocals变量
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove() 方法实现:
public void remove() {
ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
if (var1 != null) {
var1.remove(this);
}
}
四. 问题分析
1.ThreadLocalMap 里 Entry 为何声明为 WeakReference?
因为如果这里使用普通的 key-value 形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。
弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个 ThreadLocal 已经没有强引用可达,则随着它被垃圾回收,在 ThreadLocalMap 里对应的 Entry 的键值会失效,这为 ThreadLocalMap 本身的垃圾清理提供了便利。
2.什么情况下会发生内存泄漏?
发生 GC 时弱引用 Key会被回收,而 Value 不会回收,如果创建 ThreadLocal 的线程一直持续运行,并且一直不执行get、set、remove方法,这些 Key 为 null 的 Entry 的 Value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> Value,那么这个Entry对象中的 Value 就有可能一直得不到回收,从而发生内存泄漏。
3.如何避免内存泄漏?
既然 Key 是弱引用,那么我们要做的事,就是在调用 ThreadLocal 的 get()、set() 方法完成后及时调用 remove() 方法,将 Entry 节点和 Map 的引用关系移除,这样整个 Entry 对象在 GC Roots 分析后就变成不可达了,下次 GC 的时候就可以被回收了。