Java & Android 中的 {ThreadLocal.java}

当我们需要为线程提供独享信息时,应该优先使用 ThreadLocal 类。大部分开发者对于这个类都有过了解,在 Android 开发中也经常使用,但在 Android M(API Level 23) 及其之前,Android 中的 ThreadLocal 类和 JDK 中的 ThreadLocal 是有差别的(Android API Level 24 中的 ThreadLocal 类已经与 JDK 一致)。

ThreadLocal 类小小身躯,相对简单的代码却灵活的帮助我们处理了线程变量独享这个问题。分析 ThreadLocal 类源码看小身躯后边隐藏的灵活的技巧手法对于日常处理代码也是有帮助的。

接下来通过三部分来整理整篇文章:

  1. Java、Android(API Level > 23) 中的 ThreadLocal 源码分析以及使用
  2. Android(API Level <= 23) 的 ThreadLocal 源码分析以及 Android 源码中 ThreadLocal 类使用举例分析
  3. 总结

Java、Android(API Level > 23) 中的 ThreadLocal(此节分析 API Level 25) 源码分析以及使用

Java doc 中对 ThreadLocal 描述如下:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

终归就是说明了一点:为每个线程提供变量独享,其他线程不能”访问”。

使用

ThreadLocal 使用:

1
2
3
4
5
ThreadLocal<String> mThreadLocal = new ThreadLocal<String>();
// 设置值
mThreadLocal.set("value");
// 获取值
String value = mThreadLocal.get();

源码分析

ThreadLocal 使用非常简单,仔细看它的源码发现其实也很容易,接下来就一点点分析。

ThreadLocal 是一个支持泛型的类。使用 ThreadLocal 类的时候一般都是用 set(T value)get() 方法。首先看看 set(T value)

1
2
3
4
5
6
7
8
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

做的事情很简单,根据当前线程获取到 ThreadLocalMap,获取到后以当前 ThreadLocal 为 key 设置 value,没有获取到则使用当前 ThreadLocal 和 value 创建 ThreadLocalMap。

ThreadLocalMap 是什么?看上去是一个 Map。进入 getMap(t) 方法看看是怎样得到这个 ThreadLocalMap 的:

1
2
3
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

返回了 Thread(此节中所指明的 Thread 为 Android API Level 25 下的 Thread 类) 下的一个 threadLocals,该 threadLocals 就是 ThreadLocal 类下的静态类 ThreadLocalMap。此时应该能猜测到 createMap(t, value) 方法就是创建 ThreadLocalMap。代码确实如此:

1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

但是此处创建 ThreadLocalMap 传入了一个 key/value,为什么?

ThreadLocalMap 类的原始定义:

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values
ThreadLocalMap 是一个自定义的用于维护线程本地值的 hash map

ThreadLocalMap 中的静态类 Entry 用于存放 ThreadLocal 引用(弱引用)和对应的 value(看着有点像 HashMap 的实现),ThreadLocalMap 维护了一个 Entry 数组(数组的长度为 2 的指数)用于存放 entries,并使用了懒加载,只有第一次放入数据时才初始化 Entry 数组:

1
2
3
4
5
6
7
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

此时应该知道了上面 createMap 时传入 key/value 参数的目的。另外 ThreadLocalMap 还有一个构造方法用于构造 InheritableThreadLocal,由 Thread 类调用 ThreadLocal 的工厂方法 createInheritedMap(ThreadLocalMap parentMap),此构造方法主要就是将可继承的 InheritableThreadLocal 添加到当前的 InheritableThreadLocal 中。

现在大概对 ThreadLocalMap/InheritableThreadLocal 的生命周期有了个大概的了解,ThreadLocalMap/InheritableThreadLocal 在 Thread 类中,第一次需要往 Thread 的这个 map 中放入数据时会被初始化。ThreadLocalMap 中的 Entry 类是数据的载体。

接下来继续看看上面的 set(T value) 方法中的 map.set(this, value); 这行代码,这里调用了 ThreadLocal 的 set(ThreadLocal key, Object value) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

set(T value) 方法用于设置键值对。首先会根据当前 key 的索引检查 map 中是否存在此 key,存在则替换新的 value;否则检查 Key 是否为 null(可能被回收),为 null 则调用 replaceStaleEntry 方法替换 Entry;否则插入新的 Entry。其中还会做一些检查,比如是否需要扩容等。

看完了 set(T value),接下来看看 T get()

1
2
3
4
5
6
7
8
9
10
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}

get() 方法逻辑也很简单,同样调用 getMap 获取 Thread 的 ThreadLocalMap,如果获取不为 null,调用 ThreadLocalMap 的 getEntry(ThreadLocal key) 方法获取 ThreadLocal 对应 Entry,获取成功返回 Entry 中的 value,否则调用 setInitialValue() 方法设置初始值并返回(此处也进行了懒加载初始化 ThreadLocalMap),setInitialValue() 方法中逻辑和 set(T value) 类似,只不过返回了一个初始值 null。

最后再分析 remove() 方法:

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

remove() 作用就是从 Thread 中移除当前 ThreadLocal 对应的 Entry。最终调用 ThreadLocalMap 的 remove(ThreadLocal key) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}

若存在 key 对应的 Entry,清除引用对象,擦除 Entry 以及其中对应的 value,并且重新计算整理 Entry 数组数据。

Android(API Level <= 23) 的 ThreadLocal(此节分析 API Level 23) 源码分析以及 Android 源码中 ThreadLocal 类使用举例分析

Android(API Level <= 23) 中的 ThreadLocal 和 API Level > 23 实现有一点区别,下面就分析在 API Level 23 中 ThreadLocal 的实现。同样文档的描述如下:

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code ThreadLocal} object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports {@code null} values.

表达的意思和上面说过的类似:实现线程本地存储,每个线程独享变量。

使用

使用方法和上节介绍没有任何区别,同样基本方法是 set(T value)get()remove()

源码分析

在实现上和 API Level > 23 的版本有些差别。在 ThreadLcal 类中,有一个成员变量 reference,定义如下:

1
private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);

这是当前 ThreadLocal 实例的一个弱引用,用来充当存储数据结构中的 key。既然这里有了当前实例的一个弱引用作为 key,那么在存储数据结构中也就不需要定义 key 这个东西了,事实也确实如此。我们看看每个 Thread(此节中的所有 Thread 均为 API Level 23 中的 Thread 类)中存储 ThreadLocal 值的数据结构,在 Thread 源码中定义了两个成员变量 localValues 和 inheritableValues,分别代表当前线程的 local values 和可继承的 local values(和 API Level 25 中的类似,数据类型为 ThreadLocal.Values),因此 ThreadLocal.Values 就是每个线程存储 local values 的数据结构。

ThreadLocal.Values 是 ThreadLocal 的静态内类,它类似 ThreadLocalMap 用来存储每个线程的 ThreadLocal 和对应的 value。具体继续再看 ThreadLocal.Values 的源码。ThreadLocal.Values 中也有一个存储数据的数组(Object数组,长度为 2 的指数),这里的数组不再是自定义的 Entry 数组而是一个 Object 数组,在这个版本的 ThreadLocal 中,local values 的 key/value 都在这个数组中。另外还有一些其他的成员变量包括初始大小、当前大小等,值得关注的一点是在这些变量中有一个 Object 类型的 TOMBSTONE 常量用于删除的 Entry 占位,这是在 ThreadLocalMap 中没有的,具体稍后分析。

同样,我们分析三个方法。首先是 set(T value),源码如下:

1
2
3
4
5
6
7
8
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

代码几近相似,只不过这里是通过 values(Thread t) 方法获取当前线程的 Values 实例,没有找到就初始化(懒加载),找到了就通过 put(ThreadLocal<?> key, Object value) 方法设置 key/value。put(ThreadLocal<?> key, Object value) 方法和 ThreadLocalMap 中的 set 方法不同之处在于此方法中首先调用了 cleanUp() 用于清除被 GC 回收的 thread locals,然后进行了类似 ThreadLocalMap set(T value) 方法的检查、替换等流程操作。在 Values 中,所有的键/值都被存放于同一个数组中,所以 put 方法可以看到很多诸如以下的代码:

1
2
table[index] = key.reference;
table[index + 1] = value;

key 后面总是紧接着存储对应的 value。所以获取应该也是类似的读取,下面看看 T get() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}

几乎和 API Level 25 中 ThreadLocal 的 T get() 方法一样,在读取 value 时,返回的是 index 的下一个数据。

最后在看看 remove() 方法,ThreadLocal 的 remove 方法最终调用了 Values 类的 remove(ThreadLocal<?> key)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void remove(ThreadLocal<?> key) {
cleanUp();
for (int index = key.hash & mask;; index = next(index)) {
Object reference = table[index];
if (reference == key.reference) {
// Success!
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
return;
}
if (reference == null) {
// No entry found.
return;
}
}
}

同 set 方法,首先调用了 cleanUp() 用于清除被 GC 回收的 thread locals,然后寻找 key,找到则删除对应的数据,值得注意的是此处会将被删除的 key 的数据置为之前说过的 TOMBSTONE,所以 TOMBSTONE 的作用就是将所有被删除的数据的 key 替换为它本身作为标记。被 TOMBSTONE 标记的 key 在很多地方将不会使用,比如可继承的 ThreadLocal 中就不会继承 TOMBSTONE 标记的数据、每次调用 rehash() 重新计算数据时 TOMBSTONE 标记的数据也会被跳过、标记数据插入点等;另外被回收的 key 也会被标记为 TOMBSTONE。

Android 源码 Looper 中 ThreadLocal 使用

ThreadLocal 很方便的线程独享变量,在 Android 源码中也有用到。接下来分析 Android Looper 中 ThreadLocal 的使用。

在 Looper 类中一个 ThreadLocal<Looper> 类型的常量 sThreadLocal 用于操作各个 Thread 的 Looper,我们知道,在 Thread 中使用线程需要经过如下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}

首先看看 Looper.prepare() 方法,其实就是一个设置 thread local value 的过程:

1
2
3
4
5
6
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

首先调用 ThreadLocal 的 get 方法检查当前线程是否已经创建了 Looper(一个线程只能创建一个 Looper),未创建则调用 set 方法为当前此线程创建 Looper。

另外还有一个 prepareMainLooper() 方法,用于 MainLooper 创建,过程类似:

1
2
3
4
5
6
7
8
9
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

prepareMainLooper() 方法由系统在 ActivityThread 的 main() 中调用,用来设置主线程的 Looper。

既然调用了 set 方法,再看看 Looper myLooper() 方法获取 Looper,就一行代码,调用 ThreadLocal 的 get() 方法返回当前的 Looper:

1
2
3
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

总结

Java、Android(API Level > 23) 及 Android(API Level <= 23) 中的 ThreadLocal 实现原理接近,但是实现上有一点差别;在存储 ThreadLocal 作为 key 时,两者实现上都选择了 ThreadLocal 的弱引用,所以不会导致内存泄漏。