4. ThreadLocal 工作原理ThreadLocal是 Java 提供的一种线程局部变量工具它为每个线程提供一个独立的变量副本线程之间互不干扰常用于存储线程独享的数据如用户会话、数据库连接等。一句话总结ThreadLocal 让每个线程拥有自己独立的变量副本实现线程隔离。逻辑总结先拥有一个逻辑观以这个逻辑观去阅读下面的细节当我们创建一个 ThreadLocal 对象并调用它的 set(value) 方法时底层执行流程如下 ThreadLocal 的 set 方法首先获取当前线程Thread.currentThread()然后读取该线程的成员变量 threadLocals类型为 ThreadLocal.ThreadLocalMap。 这个 threadLocals 是 Thread 类的一个字段Thread 本身只负责持有这个 Map 的引用而真正的 ThreadLocalMap 类是定义在 ThreadLocal 类中的静态内部类。 如果当前线程的 threadLocals 为 null说明该线程第一次使用 ThreadLocalThreadLocal 的 set 方法会创建一个新的 ThreadLocalMap 对象并把这个新对象赋值给线程的 threadLocals 字段。 之后ThreadLocal 把当前 ThreadLocal 对象自身this作为 key把传入的 value 作为值调用这个 ThreadLocalMap 的 set 方法进行实际存储。 通过这种方式每个线程都拥有自己独立的 ThreadLocalMap从而实现线程局部变量的隔离不同线程的 set 和 get 操作互不干扰。线程的 threadLocals 初始为 null 第一次 set() 时线程中使用ThreadLocal的对象set(Object)后ThreadLocal的对象中set()方法中创建 ThreadLocalMap → 调用 map.set(ThreadLocal实例, 值) 实际存储ThreadLocalMap 的 Entry[] 数组里key弱引用(ThreadLocal)value业务数据核心数据结构Thread.threadLocals每个 Thread 对象有一个 ThreadLocal.ThreadLocalMapThreadLocalMap是ThreadLocal的静态内部类 类型的字段 threadLocals默认 null。ThreadLocalMap一个自定义的哈希表非 HashMap使用开放地址法线性探测处理冲突。EntryMap 的内部节点继承 WeakReferenceThreadLocal?key 是 ThreadLocal弱引用value 是用户数据强引用。整体关系一个 Thread → 一个 ThreadLocalMapthreadLocals。一个 ThreadLocalMap → 多个 Entry每个 Entry 对应一个 ThreadLocal → value。ThreadLocal 对象本身不存数据只作为 key 和工具。ThreadLocal 常用 API 全解析方法说明是否推荐手动调用T initialValue()返回该线程局部变量的初始值默认返回 null可重写通常重写T get()获取当前线程的线程局部变量值第一次调用时会触发 initialValue()常用void set(T value)为当前线程设置线程局部变量的值常用void remove()删除当前线程的线程局部变量非常重要防止内存泄漏强烈推荐ThreadLocal.withInitial(Supplier? extends T supplier)JDK8 提供的工厂方法推荐使用来替代重写 initialValue()详细关系图textThread 对象每个线程都有一个 │ └─ private ThreadLocal.ThreadLocalMap threadLocals; // 这个字段默认是 null │ └─ 是一个哈希表类似 HashMap但用开放地址法解决冲突 │ └─ Entry[] table 真正存储数据的数组 │ ├─ Entry key ThreadLocal 对象弱引用 WeakReference └─ value 该线程真正想存的数据强引用set() 详细流程获取当前线程Thread t Thread.currentThread();获取 t.threadLocals如果 null创建 new ThreadLocalMap(this, value)。在 map 中以当前 ThreadLocal 为 keyvalue 为值插入 Entry。计算 hashthreadLocalHashCode (table.length - 1)。线性探测插入如果冲突向后找空位如果 key 已存在覆盖 value。顺便清理过期 Entrykey 被 GC 的条目。get() 详细流程获取当前线程的 map。以 ThreadLocal 为 key 查找 Entry。如果找到返回 value否则调用 initialValue() 并 set返回。remove() 详细流程获取 map。移除对应 Entry并清理过期条目。流程图文字版textset(value): 当前线程 t Thread.currentThread() map t.threadLocals if (map null) map new ThreadLocalMap(this, value) t.threadLocals map else map.set(this, value) // this 是 ThreadLocal 对象 → 计算 hash → 线性探测 → new Entry(weakRef(this), value) get(): t Thread.currentThread() map t.threadLocals if (map ! null) Entry e map.getEntry(this) if (e ! null) return e.value return setInitialValue() // 初始化并 set remove(): t Thread.currentThread() map t.threadLocals if (map ! null) map.remove(this) // 移除 Entry 并清理5. ThreadLocal 源码分析简化版Thread 类源码片段threadLocals 字段Javapublic class Thread implements Runnable { // ThreadLocal 值属于这个线程由 ThreadLocal 类维护 ThreadLocal.ThreadLocalMap threadLocals null; // 关键字段每个线程的私有 Map // inheritableThreadLocals 用于 InheritableThreadLocal ThreadLocal.ThreadLocalMap inheritableThreadLocals null; // ... 其他字段和方法 }threadLocals存储普通 ThreadLocal 的 Map默认 null。只有第一次 set 时创建。ThreadLocal 类源码完整简化Javapublic class ThreadLocalT { // 每个 ThreadLocal 的唯一 hashCode用于 Map 索引 private final int threadLocalHashCode nextHashCode(); private static AtomicInteger nextHashCode new AtomicInteger(); private static final int HASH_INCREMENT 0x61c88647; // 魔数确保分布均匀 private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } // 初始值默认 null protected T initialValue() { return null; } // set 方法 public void set(T value) { Thread t Thread.currentThread(); ThreadLocalMap map getMap(t); if (map ! null) { map.set(this, value); } else { createMap(t, value); } } // get 方法 public T get() { Thread t Thread.currentThread(); ThreadLocalMap map getMap(t); if (map ! null) { ThreadLocalMap.Entry e map.getEntry(this); if (e ! null) { return (T) e.value; } } return setInitialValue(); } private T setInitialValue() { T value initialValue(); Thread t Thread.currentThread(); ThreadLocalMap map getMap(t); if (map ! null) { map.set(this, value); } else { createMap(t, value); } return value; } // remove 方法 public void remove() { ThreadLocalMap m getMap(Thread.currentThread()); if (m ! null) { m.remove(this); } } // 获取线程的 Map ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // 创建 Map void createMap(Thread t, T firstValue) { t.threadLocals new ThreadLocalMap(this, firstValue); } // JDK8 工厂方法 public static S ThreadLocalS withInitial(Supplier? extends S supplier) { return new SuppliedThreadLocal(supplier); } static class SuppliedThreadLocalT extends ThreadLocalT { private final Supplier? extends T supplier; SuppliedThreadLocal(Supplier? extends T supplier) { this.supplier Objects.requireNonNull(supplier); } Override protected T initialValue() { return supplier.get(); } } }从ThreadLocal源码中可以看出在set方法中首先获取当前线程在获取当前线程的Map就是threadlocals实际进行存储是ThreadLocalMap中的set方法。面向对象编程对ThreadLocalMap的类型的操作肯定是定义在ThreadLocalMap类中的所以ThreadLocal调用set方法实际上是调用内部类ThreadLocal.ThreadLocalMap中的set方法。ThreadLocalMap 是定义在 ThreadLocal 类内部的静态内部类而不是直接定义在 Thread 类里面。Javapublic class ThreadLocalT { // ... 其他代码 /** * ThreadLocalMap 是 ThreadLocal 的一个内部静态类 * 它是一个自定义的哈希表专门用来存储每个线程的 ThreadLocal 变量 */ static class ThreadLocalMap { // Entry 继承 WeakReference // 内部静态类每个元素就是一个 Entry static class Entry extends WeakReferenceThreadLocal? { Object value; // 强引用真正存放的用户数据 Entry(ThreadLocal? k, Object v) { super(k); // 弱引用key指向 ThreadLocal 对象 value v; } } private Entry[] table; // 核心Entry 数组哈希桶 private int size 0; // 当前元素个数 private int threshold; // 扩容阈值通常为 table.length * 2/3 // 构造方法首次 set 时调用 ThreadLocalMap(ThreadLocal? firstKey, Object firstValue) { ... } } // Thread 类中持有的就是这个 ThreadLocalMap 的引用 // 在 Thread 类源码中 // ThreadLocal.ThreadLocalMap threadLocals null; }Entry 类自己只声明了一个字段value 但它实际上还有一个“隐藏”的引用——来自父类 WeakReference 的那个引用。通过 super(k) 调用 WeakReference 的构造方法。而这个k就是Threadlocal的实例作为key。ThreadLocalMap 类源码简化Javastatic class ThreadLocalMap { // Entrykey 弱引用 static class Entry extends WeakReferenceThreadLocal? { Object value; Entry(ThreadLocal? k, Object v) { super(k); value v; } } private static final int INITIAL_CAPACITY 16; private Entry[] table; private int size 0; private int threshold; ThreadLocalMap(ThreadLocal? firstKey, Object firstValue) { table new Entry[INITIAL_CAPACITY]; int i firstKey.threadLocalHashCode (INITIAL_CAPACITY - 1); table[i] new Entry(firstKey, firstValue); size 1; setThreshold(INITIAL_CAPACITY); } // set 方法 private void set(ThreadLocal? key, Object value) { Entry[] tab table; int len tab.length; int i key.threadLocalHashCode (len - 1); for (Entry e tab[i]; e ! null; e tab[i nextIndex(i, len)]) { ThreadLocal? k e.get(); if (k key) { e.value value; return; } if (k null) { replaceStaleEntry(key, value, i); return; } } tab[i] new Entry(key, value); int sz size; if (!cleanSomeSlots(i, sz) sz threshold) { rehash(); } } // getEntry 方法简化 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); } } // remove 方法 private void remove(ThreadLocal? key) { Entry[] tab table; int len tab.length; int i key.threadLocalHashCode (len - 1); for (Entry e tab[i]; e ! null; e tab[i nextIndex(i, len)]) { if (e.get() key) { e.clear(); // 清弱引用 expungeStaleEntry(i); return; } } } // ... 其他辅助方法如 expungeStaleEntry清理过期、rehash扩容等 }关键点ThreadLocalMap 不是标准 HashMap使用数组 线性探测。扩容阈值是容量的 2/3。清理过期 Entry 在 set/get/remove 时发生。为什么设计成 ThreadLocal 的内部类而不是 Thread 的内部类逻辑归属ThreadLocalMap 是为 ThreadLocal 服务的工具类主要负责管理 ThreadLocal 的键值对所以放在 ThreadLocal 内部更符合封装原则。访问权限作为 ThreadLocal 的静态内部类它可以方便地访问 ThreadLocal 的私有成员如 hashCode 计算逻辑。Thread 只负责持有引用Thread 只是“拥有”一个 ThreadLocalMap 实例但不负责它的实现细节。基本代码示例Javapublic class ThreadLocalDemo { // 1. 定义 ThreadLocal 变量 private static final ThreadLocalString USER_NAME new ThreadLocal(); private static final ThreadLocalInteger USER_ID new ThreadLocal(); public static void main(String[] args) { // 线程1 new Thread(() - { USER_NAME.set(张三); USER_ID.set(1001); System.out.println(线程1: USER_NAME.get() , ID: USER_ID.get()); // 业务逻辑... USER_NAME.remove(); // 强烈推荐手动清理 USER_ID.remove(); }).start(); // 线程2 new Thread(() - { USER_NAME.set(李四); USER_ID.set(1002); System.out.println(线程2: USER_NAME.get() , ID: USER_ID.get()); USER_NAME.remove(); USER_ID.remove(); }).start(); } }输出顺序不定text线程1: 张三, ID: 1001 线程2: 李四, ID: 1002生产开发代码使用简单的一个生产环境demo常见的 ThreadLocal 工具类 写法把 ThreadLocal 及其操作方法都定义成 static让它像一个全局的“上下文工具”一样被使用package com.sky.context; public class BaseContext { public static ThreadLocalLong threadLocal new ThreadLocal(); public static void setCurrentId(Long id) { threadLocal.set(id); } public static Long getCurrentId() { return threadLocal.get(); } public static void removeCurrentId() { threadLocal.remove(); } }通常会结合Spring Interceptor或Filter做统一处理JavaComponent public class UserContextInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { Long userId getUserIdFromTokenOrHeader(request); if (userId ! null) { BaseContext.setCurrentId(userId); } return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { BaseContext.removeCurrentId(); // 100% 清理 }防止内存泄漏。真实的生产环境使用把上下文封装成对象更清晰、更安全/** * 当前登录用户信息上下文推荐写法 */ public final class CurrentUser { private static final ThreadLocalUserInfo HOLDER new ThreadLocal(); private CurrentUser() {} // 禁止实例化 public static void set(UserInfo user) { HOLDER.set(user); } public static UserInfo get() { return HOLDER.get(); } public static Long getId() { UserInfo u get(); return u ! null ? u.getId() : null; } public static String getUsername() { UserInfo u get(); return u ! null ? u.getUsername() : null; } public static String getTenantId() { UserInfo u get(); return u ! null ? u.getTenantId() : null; } public static void clear() { HOLDER.remove(); } // 常用快捷方法 public static boolean isLogin() { return get() ! null; } }优点扩展性好以后加字段不用改工具类签名类型安全语义更清晰其中isLogin() 这个方法的作用非常简单且实用它只是判断 当前线程中是否已经成功设置了用户上下文即 CurrentUser.set(userInfo) 被调用过且还没有被 clear()。在 Spring MVC 项目 中在拦截器里统一拦截需要登录的请求。完整示例推荐写法JavaComponent public class LoginInterceptor implements HandlerInterceptor { // 不需要登录的路径白名单 private static final ListString EXCLUDE_PATHS Arrays.asList( /api/login, /api/register, /api/public/**, /error ); Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI request.getRequestURI(); // 白名单直接放行 if (isExcludePath(requestURI)) { return true; } // 检查是否已登录 if (!CurrentUser.isLogin()) { // 返回 401 未授权 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write({\code\:401,\message\:\请先登录\}); return false; // 拦截不继续执行 Controller } // 已登录继续执行 return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束一定要清理 ThreadLocal防止线程池污染 CurrentUser.clear(); } private boolean isExcludePath(String uri) { return EXCLUDE_PATHS.stream().anyMatch(pattern - uri.startsWith(pattern.replace(/**, )) || AntPathRequestMatcher.antMatcher(pattern).matches(uri) ); } }注册拦截器在 WebMvcConfigurer 中JavaConfiguration public class WebConfig implements WebMvcConfigurer { Autowired private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) // 拦截所有请求 .excludePathPatterns(/api/login, /api/register, /public/**); // 排除白名单 } }强烈推荐把登录检查逻辑放在Interceptor的 preHandle 中统一处理而不是散落在每个 Controller 里。这样维护性更好。一定要在 afterCompletion 中调用 CurrentUser.clear()否则在线程池Tomcat 默认线程池环境下会出现严重的数据污染。线程池场景最推荐Javapublic class LoginUserContext { private static final TransmittableThreadLocalLoginUser CONTEXT new TransmittableThreadLocal(); // 同上 set/get/remove/getUserId 等方法 // 线程池要包装 Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); // ... 配置 executor.setTaskDecorator(TtlRunnable::new); // 或使用 TtlExecutors 包装 return executor; } }6. ThreadLocal 内存泄漏分析会不会泄漏是的尤其在线程池中。但不是 ThreadLocal 本身泄漏而是 value 对象。原因keyThreadLocal是弱引用当 ThreadLocal 实例无强引用时被 GC。value 是强引用key GC 后Entry 仍持有 value只要线程存活value 不释放。线程池复用线程线程不销毁threadLocals 不清导致 value 积累。解决方案手动 remove()。使用 finally 块。线程结束时自动清理但线程池不结束。7. Java 四种引用类型与泄漏相关类型描述GC 行为ThreadLocal 关联强引用普通引用如 Object o new Object()不会回收value 是强引用软引用SoftReferenceT sr new SoftReference(obj)内存不足时回收无直接关联弱引用WeakReferenceT wr new WeakReference(obj)下次 GC 回收key 是弱引用虚引用PhantomReferenceT pr new PhantomReference(obj, queue)随时回收用于 finalize无强引用如果value是数值型对应就会自动装箱为包赚类。8. 线程池环境下的坑问题复用 ThreadLocal 数据污染/泄漏线程池线程复用上个任务的 value 残留下个任务 get() 拿到脏数据。示例Tomcat/ExecutorService 中未 remove 导致用户会话混淆。解决方案手动 remove() 在 finally。手动 remove()最推荐Javatry { // UserContext是new的Threadlocal UserContext.set(user); // 业务 } finally { UserContext.remove(); }使用 TransmittableThreadLocal (TTL阿里巴巴库)支持线程池传递。自定义 ThreadPoolExecutor重写 afterExecute() 清理。9. InheritableThreadLocal父子线程共享特点子线程继承父线程的值new Thread() 时复制到 inheritableThreadLocals。示例Javaprivate static final InheritableThreadLocalString INHERIT new InheritableThreadLocal(); INHERIT.set(Parent Value); new Thread(() - System.out.println(INHERIT.get())).start(); // 输出: Parent Value局限只在创建子线程时复制不支持线程池复用。仍有泄漏风险。推荐TTL 替代支持线程池。10. 总结与最佳实践优点简单、高效、线程安全。缺点潜在泄漏需要手动管理。黄金法则用 withInitial() 创建。set 后必须 remove()。线程池用 TTL。避免存大对象。测试时检查内存使用。