Android随笔-ThreadLocal
·
ThreadLocal 是 Java 提供的线程本地存储机制,作用可以概括为:让每个线程拥有独立的变量副本,线程之间互不干扰。
一、核心作用
| 作用 | 说明 |
|---|---|
| 线程隔离 | 每个线程访问的是自己的变量,不存在竞争 |
| 避免传参 | 不需要通过方法参数层层传递上下文 |
| 线程安全 | 天然线程安全,无需加锁 |
二、在 Android 中的典型应用
1. Looper 的线程绑定(你最熟悉的)
// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
return sThreadLocal.get(); // 获取当前线程的 Looper
}
效果:
- 主线程调用 prepare() → sThreadLocal 存主线程的 Looper
- 子线程调用 prepare() → sThreadLocal 存子线程的 Looper
- 两个线程互不影响,各自取到自己的 Looper
2. Choreographer 的线程绑定
// Choreographer.java
private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<>();
public static Choreographer getInstance() {
return sThreadInstance.get(); // 每个线程一个 Choreographer
}
3. Activity 的 UI 线程检查
// View.java
static ThreadLocal<RenderNode> sThreadLocalRenderNode = new ThreadLocal<>();
// 确保 RenderNode 只在创建它的线程使用
RenderNode getRenderNode() {
RenderNode node = sThreadLocalRenderNode.get();
if (node == null) {
node = new RenderNode();
sThreadLocalRenderNode.set(node);
}
return node;
}
三、源码实现
核心结构
public class ThreadLocal<T> {
// 获取当前线程的 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();
}
// 设置值
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
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
ThreadLocal 的本质是 线程私有的键值对存储,底层数据结构是 ThreadLocalMap(哈希表),实现方式是每个线程自带一个 ThreadLocalMap,ThreadLocal 对象本身作为 key。
底层数据结构
// Thread.java
public class Thread implements Runnable {
// 每个线程自带一个 ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
}
// ThreadLocalMap 是 ThreadLocal 的静态内部类
static class ThreadLocalMap {
// Entry 使用弱引用,key 是 ThreadLocal 本身
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 实际存储的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 哈希表,初始容量 16
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
// 用 ThreadLocal 的 hashCode 计算索引
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);
}
}
关键点: 同一个 local 对象,hash 值相同,但操作的是不同线程的 Map,所以拿到不同 value。
为什么不用链表?
- 数据量小(一个线程通常只有几个 ThreadLocal)
- 开放寻址法缓存友好,遍历数组即可
- 冲突时自动清理 key 为 null 的脏 Entry
四、内存结构图

- 数据实际存在 Thread 对象内部
- ThreadLocal 只负责 计算哈希、定位槽位
- 同一个 ThreadLocal 对象,在不同线程中定位到 不同的槽位 → 不同的 value
五、关键特性
| 特性 | 说明 |
|---|---|
| 弱引用 key | ThreadLocal 作为 key 是弱引用,防止内存泄漏(但 value 是强引用) |
| 自动清理 | 线程退出时,ThreadLocalMap 被回收 |
| 哈希冲突 | 使用开放寻址法解决冲突,不是链表 |
| 扩容 | 容量达到 2/3 时扩容为 2 倍 |
六、内存泄漏风险
// 线程池中使用 ThreadLocal,线程不会退出
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(() -> {
threadLocal.set(bigObject); // 大对象
// 忘记 remove()
});
// 问题:线程复用,bigObject 一直持有,无法 GC
解决方案:
try {
threadLocal.set(value);
// 使用...
} finally {
threadLocal.remove(); // 必须手动清理
}
七、总结
ThreadLocal = 线程的"私人储物柜"。同一个 ThreadLocal 对象,在不同线程里打开,拿到的是不同的东西。
更多推荐



所有评论(0)