ThreadLocal 可简单理解为 Thread 的局部变量,在线程的生命周期内起作用。

ThreadLocal 原理

ThreadLocal 类的内部有 ThreadLocalMap内部类,Entry 又是 ThreadLocalMap 类里的内部类。

ThreadLocalMap 是一个为 ThreadLocal 提供服务的 “customized hash map” 。

Entry 是一个继承 WeakReference 的类,是 ThreadLocalMap 的构成类型,Entry 的中的 key 即为 ThreadLocal。

Thread 类里声明了 ThreadLocalMap。

1
2
3
/* ThreadLocal values pertaining to this thread. */
/* This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

接下来分析下 ThreadLocal 类的代码 (Android SDK 25)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取与当前线程绑定的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从 HashMap 里获取当前 ThreadLocal 的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
// 当前线程没有 ThreadLocalMap 对象,初始化 ThreadLocalMap
return setInitialValue();
}

private T setInitialValue() {
// 调用我们 Override 的方法来初始化,默认的 initialValue() 返回 null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

除此之外的 setcreateMapgetMap 等的方法都是些常见 HashMap 的操作。

ThreadLocal 使用不当造成的内存泄漏

因为 ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,所以当系统 GC ,ThreadLocal对象被回收,ThreadLocalMap 中就会有 key 为空的 Entry,这个 Entry 的 value 就没法访问了,要是线程由于种种原因没有结束的话(譬如作为线程池的一员),那么 key 为空的 Entry 中的 value 由于被强引用 Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value 导致没法被回收,造成内存泄漏。

那么需要怎么做来避免泄漏呢?

其实当调用 ThreadLocal 的 getsetremove 的时候都会调用到 ThreadLocalMap 里的方法将 key 为空的 Entry 的 value 置空。

所以正确的做法是在用完 Threadlocal 后,都调用 remove 方法来清除数据。