Android随笔-ThreadLocal

📅 2026/7/6 23:41:03 👁️ 阅读次数 📝 编程学习
Android随笔-ThreadLocal

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(哈希表),实现方式是每个线程自带一个ThreadLocalMapThreadLocal 对象本身作为 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

五、关键特性

特性说明
弱引用 keyThreadLocal 作为 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 对象,在不同线程里打开,拿到的是不同的东西。