Android随笔-ThreadLocal
ThreadLocal 是 Java 提供的线程本地存储机制作用可以概括为让每个线程拥有独立的变量副本线程之间互不干扰。一、核心作用作用说明线程隔离每个线程访问的是自己的变量不存在竞争避免传参不需要通过方法参数层层传递上下文线程安全天然线程安全无需加锁二、在 Android 中的典型应用1. Looper 的线程绑定你最熟悉的// Looper.javastaticfinalThreadLocalLoopersThreadLocalnewThreadLocal();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两个线程互不影响各自取到自己的 Looper2. Choreographer 的线程绑定// Choreographer.javaprivatestaticfinalThreadLocalChoreographersThreadInstancenewThreadLocal();publicstaticChoreographergetInstance(){returnsThreadInstance.get();// 每个线程一个 Choreographer}3. Activity 的 UI 线程检查// View.javastaticThreadLocalRenderNodesThreadLocalRenderNodenewThreadLocal();// 确保 RenderNode 只在创建它的线程使用RenderNodegetRenderNode(){RenderNodenodesThreadLocalRenderNode.get();if(nodenull){nodenewRenderNode();sThreadLocalRenderNode.set(node);}returnnode;}三、源码实现核心结构publicclassThreadLocalT{// 获取当前线程的 ThreadLocalMappublicTget(){ThreadtThread.currentThread();ThreadLocalMapmapgetMap(t);if(map!null){ThreadLocalMap.Entryemap.getEntry(this);if(e!null){SuppressWarnings(unchecked)Tresult(T)e.value;returnresult;}}returnsetInitialValue();}// 设置值publicvoidset(Tvalue){ThreadtThread.currentThread();ThreadLocalMapmapgetMap(t);if(map!null)map.set(this,value);elsecreateMap(t,value);}// 获取线程的 ThreadLocalMapThreadLocalMapgetMap(Threadt){returnt.threadLocals;}}ThreadLocal 的本质是线程私有的键值对存储底层数据结构是ThreadLocalMap哈希表实现方式是每个线程自带一个ThreadLocalMapThreadLocal 对象本身作为 key。底层数据结构// Thread.javapublicclassThreadimplementsRunnable{// 每个线程自带一个 ThreadLocalMapThreadLocal.ThreadLocalMapthreadLocalsnull;}// ThreadLocalMap 是 ThreadLocal 的静态内部类staticclassThreadLocalMap{// Entry 使用弱引用key 是 ThreadLocal 本身staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;// 实际存储的值Entry(ThreadLocal?k,Objectv){super(k);valuev;}}// 哈希表初始容量 16privatestaticfinalintINITIAL_CAPACITY16;privateEntry[]table;// 用 ThreadLocal 的 hashCode 计算索引privateEntrygetEntry(ThreadLocal?key){intikey.threadLocalHashCode(table.length-1);Entryetable[i];if(e!nulle.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线程不会退出ExecutorServiceexecutorExecutors.newFixedThreadPool(4);executor.execute(()-{threadLocal.set(bigObject);// 大对象// 忘记 remove()});// 问题线程复用bigObject 一直持有无法 GC解决方案try{threadLocal.set(value);// 使用...}finally{threadLocal.remove();// 必须手动清理}七、总结ThreadLocal 线程的私人储物柜。同一个 ThreadLocal 对象在不同线程里打开拿到的是不同的东西。