ThreadLocal 源码分析
在 Android 的 Handler 机制下,ThreadLocal
提供了对不同线程本地变量的副本的保存,并且实现了线程数据的隔离,不同线程的数据不会产生错乱。且在一个线程结束后,其对应在 ThreadLocal
内的数据会被释放,除非有其他地方对这部分数据的引用还存在。
基本介绍
ThreadLocal
提供了线程本地变量保存的功能。线程本地变量的修改由 ThreadLocal
的 set()
实现,读取由 ThreadLocal
的 get()
方法实现。ThreadLocal
实例通常会被定义成 static
字段,这些字段与一个线程的状态关联 ( 例如,用户ID 或 业务ID ) 。
依据 app 启动过程,主线程下 ThreadLocal
的使用进行对应的分析。
Android 主线程 ThreadLocal
的使用
在 Android 主线程中,ThreadLocal
对象被 Looper
所持有,在 Looper
类中被定义 static
字段。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
在 app 进程开始执行时,先执行 ActivityThread.main()
方法:
// ActivityThread.java
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// ...
Looper.prepareMainLooper(); // 主线程下,准备main looper。
// ...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// ...
Looper.loop(); // looper开始循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程中,通过调用 Looper.prepareMainLooper()
创建并初始化 Looper
及 ThreadLocal
对象。调用到 Looper.prepareMainLooper()
方法:
// Looper.java
// sThreadLocal.get() 若是在 prepare() 方法调用之前返回,会返回 null。
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@UnsupportedAppUsage
private static Looper sMainLooper; // 这个对象是static的,即它的生命周期跟app一致。
public static void prepare() {
prepare(true);
}
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)); // 设置当前线程与looper关联
}
// 主线程的 main looper 由系统环境创建,因此不需要在 app 程序中调用。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
// 返回与当前线程关联的 Looper 对象,若当前线程还没有关联的 Looper 对象,则返回 null。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在一个线程中,只能创建一个 Looper
对象,否则对应线程就发生crash,包含提示信息 "Only one Looper may be created per thread"
。下面时序图是 app 进程中调用 prepareMainLooper()
的调用过程。
总结起来也就简单几部:
- 判断当前线程是否已经有与之关联的 Looper 对象了,若有则抛出运行时错误 (
throw new RuntimeException("Only one Looper may be created per thread");
)。 - 通过
sThreadLocal.set()
将新建的Looper
对象与当前线程关联起来。 - 判断
sMainLooper
若是不为 null,抛出状态异常 (throw new IllegalStateException("The main Looper has already been prepared."
))。 - 从
sThreadLocal
中获取与当前线程关联的Looper
对象,作为主线程Looper
赋给sMainLooper
。
主线程的 looper 创建之后,即 sThreadLocal
中保存有 sMainLooper
引用的对象之后。looper 开始进行 loop()
操作 (后面专门讲 Looper)。
下面 TreadLocal
最常用的方法 set()
get()
方法分析。
set()
在 app进程运行时,会创建 main looper,并将其与主线程关联。也就是调用 sThreadLocal.set(new Looper(quitAllowed))
的过程。
set()
方法的定义:
// ThreadLocal.java
private final int threadLocalHashCode = nextHashCode();
// 这个是 static 的,在线程中创建 ThreadLocal 并调用 set() 方法, nextHashCode 的值在每次调用后增长,
// 并被用于计算 Entry 在表中的索引位置值。
private static AtomicInteger nextHashCode = new AtomicInteger();
// hash值增量,每次增加后,与 0x0F(即15) 作且运算,每次计算产生的索引值不同,
// 在计算第16次时,索引值重新开始,与第一次计算的循环值相同。
private static final int HASH_INCREMENT = 0x61c88647;
// 用于增长并非原来的值
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 将线程局部变量的当前线程福报设置给特定值。
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 首次调用的时候,getMap(t)方法返回null。
if (map != null)
map.set(this, value); // 当 ThreadLocalMap 已经创建,则直接调用 set 方法。
else
createMap(t, value); // 首次调用 getMap(t) 返回Null,因此会执行到这里,创建 ThreadLocalMap 对象。
}
// 获取当前线程的线程本地变量map。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 获取当前线程的本地变量表。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建当前线程的 ThreadLocalMap 对象。
void createMap(Thread t, T firstValue) { // 这里主线程中,传入的 firstValue 是 Looper 对象。
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocal 内部的 ThreadLocalMap,实际 保存线程本地变量的 实现。
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private Entry[] table; // 封装了 (ThreadLocal, Looper) 对的结构类。
private int size = 0; // 存储有值的 entry 表大小。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 创建保存 (firstKey, firstValue) 键值对的 ThreadLocalMaps 对象,其中 firstKey 总是 ThreadLocal。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 这个方式产生存放 Entry 的索引值。
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY); // 当 table 数组满时需要扩展表的大小。
}
}
每次 首次 通过 当前线程对象 调用 getMap(t)
方法尝试获取线程本地变量 map 结果,该方法都会返回 null,因此会调用到 createMap(t, value);
若已经创建了,则调用到 ThreadLocalMap.set(ThreadLocal<?> key, Object value)
。
// ThreadLocalMap in ThreadLocal.java
// 快速计算下一个索引位置。
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private void set(ThreadLocal<?> key, Object value) { // 主线程中,value 的值是 Looper 对象。
Entry[] tab = table;
int len = tab.length; // 一般就是数组大小的 16,或扩展后的容量大小。
int i = key.threadLocalHashCode & (len-1);
// 在 Entry[] 中查找, 若搜索到了相同的 ThreadLocal 的 Entry,直接更新对应的 value。
for (Entry e = tab[i];
e != null;
// 查找下一个搜索位置。
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 搜索到了相同的 ThreadLocal 的 Entry,直接更新对应的 value。
e.value = value;
return;
}
if (k == null) { // Entry 的 ThreadLcoal<?> 是 null,替换 Entry 对象。
// 在当前运行中清除所有过期条目。
// 主要目的是在插入新元素或者清除过期元素时,清除哈希表中的过期条目,以减少哈希表的大小并提高效率。
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这段代码是 ThreadLocal
的 set(T)
方法的真正实现,在设置过程中,清理掉 ThreadLocalMap
中的过期条目,过期的判断条件就是 Entry
对象是否被 GC 回收了,即 e.get() == null
。
get()
get()
方法定义:
// 返回当前线程局部变量表的副本。若表中没有值,则返回 setInitialValue() 方法中设置的初始值。
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();
}
static class ThreadLocalMap {
// 通过 hashcode 计算 Entry 对象的索引位置。 并获取对应值,若被GC回收了,则调用到 getEntryAfterMiss()。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
// 在直接计算得到的索引位置处无法找到对应的 Entry 时,计算下一个位置,尝试搜索。
// 若 e.get() == null,表示该条目过期,会对该索引位置 Entry 清理。
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
}
上述的 get()
set()
方法是 ThreadLocal
中最常用的两个方法。对于 ThreadLocal
的最常用分析先到这里。