ThreadLocal 是 Java 提供的线程本地存储机制,作用可以概括为:让每个线程拥有独立的变量副本,线程之间互不干扰。
一、核心作用
| 作用 | 说明 |
|---|---|
| 线程隔离 | 每个线程访问的是自己的变量,不存在竞争 |
| 避免传参 | 不需要通过方法参数层层传递上下文 |
| 线程安全 | 天然线程安全,无需加锁 |
二、在 Android 中的典型应用
1. Looper 的线程绑定(你最熟悉的)
// Looper.javastaticfinalThreadLocal<Looper>sThreadLocal=newThreadLocal<>();publicstaticvoidprepare(){if(sThreadLocal.get()!=null){thrownewRuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(newLooper(quitAllowed));}publicstaticLoopermyLooper(){returnsThreadLocal.get();// 获取当前线程的 Looper}效果:
- 主线程调用 prepare() → sThreadLocal 存主线程的 Looper
- 子线程调用 prepare() → sThreadLocal 存子线程的 Looper
- 两个线程互不影响,各自取到自己的 Looper
2. Choreographer 的线程绑定
// Choreographer.javaprivatestaticfinalThreadLocal<Choreographer>sThreadInstance=newThreadLocal<>();publicstaticChoreographergetInstance(){returnsThreadInstance.get();// 每个线程一个 Choreographer}3. Activity 的 UI 线程检查
// View.javastaticThreadLocal<RenderNode>sThreadLocalRenderNode=newThreadLocal<>();// 确保 RenderNode 只在创建它的线程使用RenderNodegetRenderNode(){RenderNodenode=sThreadLocalRenderNode.get();if(node==null){node=newRenderNode();sThreadLocalRenderNode.set(node);}returnnode;}三、源码实现
核心结构
publicclassThreadLocal<T>{// 获取当前线程的 ThreadLocalMappublicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")Tresult=(T)e.value;returnresult;}}returnsetInitialValue();}// 设置值publicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);}// 获取线程的 ThreadLocalMapThreadLocalMapgetMap(Threadt){returnt.threadLocals;}}ThreadLocal 的本质是线程私有的键值对存储,底层数据结构是ThreadLocalMap(哈希表),实现方式是每个线程自带一个ThreadLocalMap,ThreadLocal 对象本身作为 key。
底层数据结构
// Thread.javapublicclassThreadimplementsRunnable{// 每个线程自带一个 ThreadLocalMapThreadLocal.ThreadLocalMapthreadLocals=null;}// ThreadLocalMap 是 ThreadLocal 的静态内部类staticclassThreadLocalMap{// Entry 使用弱引用,key 是 ThreadLocal 本身staticclassEntryextendsWeakReference<ThreadLocal<?>>{Objectvalue;// 实际存储的值Entry(ThreadLocal<?>k,Objectv){super(k);value=v;}}// 哈希表,初始容量 16privatestaticfinalintINITIAL_CAPACITY=16;privateEntry[]table;// 用 ThreadLocal 的 hashCode 计算索引privateEntrygetEntry(ThreadLocal<?>key){inti=key.threadLocalHashCode&(table.length-1);Entrye=table[i];if(e!=null&&e.get()==key)returne;elsereturngetEntryAfterMiss(key,i,e);}}关键点:同一个 local 对象,hash 值相同,但操作的是不同线程的 Map,所以拿到不同 value。
为什么不用链表?
- 数据量小(一个线程通常只有几个 ThreadLocal)
- 开放寻址法缓存友好,遍历数组即可
- 冲突时自动清理 key 为 null 的脏 Entry
四、内存结构图
- 数据实际存在 Thread 对象内部
- ThreadLocal 只负责 计算哈希、定位槽位
- 同一个 ThreadLocal 对象,在不同线程中定位到 不同的槽位 → 不同的 value
五、关键特性
| 特性 | 说明 |
|---|---|
| 弱引用 key | ThreadLocal 作为 key 是弱引用,防止内存泄漏(但 value 是强引用) |
| 自动清理 | 线程退出时,ThreadLocalMap 被回收 |
| 哈希冲突 | 使用开放寻址法解决冲突,不是链表 |
| 扩容 | 容量达到 2/3 时扩容为 2 倍 |
六、内存泄漏风险
// 线程池中使用 ThreadLocal,线程不会退出ExecutorServiceexecutor=Executors.newFixedThreadPool(4);executor.execute(()->{threadLocal.set(bigObject);// 大对象// 忘记 remove()});// 问题:线程复用,bigObject 一直持有,无法 GC解决方案:
try{threadLocal.set(value);// 使用...}finally{threadLocal.remove();// 必须手动清理}七、总结
ThreadLocal = 线程的"私人储物柜"。同一个 ThreadLocal 对象,在不同线程里打开,拿到的是不同的东西。