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(哈希表),实现方式是每个线程自带一个 ThreadLocalMapThreadLocal 对象本身作为 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 对象,在不同线程里打开,拿到的是不同的东西。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐