1. ThreadLocal 简介
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,并且这些数据只能在相应的线程中获取到。在日常开发中,虽然 ThreadLocal 使用场景较少,但在某些特定场景下,ThreadLocal 能简化一些复杂功能的实现。例如,在 Looper、ActivityThread 和 AMS(Activity Manager Service) 中都用到了 ThreadLocal。
通常,当数据是以线程为作用域,并且不同线程需要不同的数据副本时,就可以考虑使用 ThreadLocal。例如,对于 Handler 来说,它需要获取当前线程的 Looper,而 Looper 是一个以线程为作用域的类,不同线程拥有不同的 Looper。
2. ThreadLocal 基本用法
首先定义一个 ThreadLocal 对象,这里选择 Boolean 类型,如下:
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();
然后分别在主线程、子线程 1 和子线程 2 中设置和访问它的值,代码如下:
private static void test() {
mThreadLocal.set(true);
Log.d(TAG, "[Thread#main] mThreadLocal=" + mThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
mThreadLocal.set(false);
Log.d(TAG, "[Thread#1] mThreadLocal=" + mThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
Log.d(TAG, "[Thread#2] mThreadLocal=" + mThreadLocal.get());
}
}.start();
}
在上述代码中,主线程设置了 mThreadLocal 的值为 true,子线程 1 设置了 mThreadLocal 的值为 false,而子线程 2 中没有设置 mThreadLocal 的值,并在三个线程中通过 get 方法获取 mThreadLocal 的值。主线程中值为 true,子线程 1 中值为 false,而子线程 2 由于没有设置值,值应为 null。
运行程序,日志如下:
D/MainActivity(946): [Thread#main] mThreadLocal=true
D/MainActivity(946): [Thread#1] mThreadLocal=false
D/MainActivity(946): [Thread#2] mThreadLocal=null
从上面的日志可以看出,虽然在不同线程中访问的是同一个 ThreadLocal 对象,但每个线程中访问到的值都不一样。这是因为每个线程都有自己的 ThreadLocalMap,而 ThreadLocalMap 是一个存储线程局部变量的结构。ThreadLocal 并不直接存储变量的值,而是通过当前线程的 ThreadLocalMap 来存储和获取变量的值。
3. ThreadLocal 的工作原理
ThreadLocal 类结构
ThreadLocal 类的核心方法包括:
get()set(T value)remove()initialValue()
ThreadLocal 通过 Thread 类中的一个内部类 ThreadLocalMap 来实现线程局部变量存储。每个线程对象中都有一个 ThreadLocalMap 实例,用于存储线程局部变量。
ThreadLocalMap 结构
ThreadLocalMap 是一个自定义的哈希表,键是 ThreadLocal 对象,值是线程局部变量。ThreadLocalMap 的设计使用了弱引用的键,以便当 ThreadLocal 实例不再被引用时,可以被垃圾回收,从而避免内存泄漏。
以下是 ThreadLocalMap 的简化代码结构:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private int size = 0;
// 省略构造方法及其他实现
}
get() 方法
当调用 get 方法时,ThreadLocal 通过当前线程的 ThreadLocalMap 获取线程局部变量的值:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
return setInitialValue();
}
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;
}
get 方法首先获取当前线程的 ThreadLocalMap,然后从中查找当前 ThreadLocal 的值。如果找不到,则调用 setInitialValue 初始化一个值。
set() 方法
set 方法用于设置线程局部变量的值:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set 方法同样通过当前线程的 ThreadLocalMap 设置变量值,如果 ThreadLocalMap 不存在,则创建一个新的 ThreadLocalMap。
remove() 方法
remove 方法用于移除当前线程的局部变量:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
4. 内存泄漏问题
尽管 ThreadLocal 通过弱引用机制减少了内存泄漏的可能,但不正确的使用仍然可能导致内存泄漏。例如,如果在线程池中使用 ThreadLocal,没有显式调用 remove 方法来清理数据,线程对象可能会长期存在,导致内存泄漏。因此,在使用 ThreadLocal 时,务必在不再需要时调用 remove 方法清理数据。