




ThreadLocal通过为每个线程维护独立变量副本解决线程间数据污染,核心是“线程内单例”;需显式remove()避免内存泄漏,且子线程默认不继承其值。
多线程环境下,多个线程共享同一个对象实例时,如果该对象里有可变状态(比如一个 SimpleDateFormat 实例),就容易因并发修改导致结果错乱或抛出 java.lang.ArrayIndexOutOfBoundsException 这类诡异异常。ThreadLocal 的核心作用不是“传参”或“全局变量”,而是为每个线程单独维护一份变量副本,实现逻辑上的“线程内单例”。
userId)、事务上下文(TransactionStatus)或数据库连接(Connection)绑定到当前线程,避免层层手动传递++ 操作),而是绕过共享——让每个线程压根不共享那个变量ThreadLocal 变量本身是静态的,但其内部的值(value)存储在各线程自己的 ThreadLocalMap 中,生命周期与线程绑定调用 get() 时若未执行过 set(),且没重写 initialValue() 方法,会返回 null(对引用类型)或默认值(如 0 对 int)。这不是 bug,而是设计使然:ThreadLocal 不主动初始化,由使用者决定何时、如何提供初始值。
private static final ThreadLocalDATE_FORMAT = ThreadLocal.withInitia l(() -> new SimpleDateFormat("yyyy-MM-dd"));
if (dateFormatter.get() == null) { dateFormatter.set(new SimpleDateFormat("...")); }get() 返回非空对象,又忘记初始化,运行时可能直接触发 NullPointerException
内存泄漏不是因为 ThreadLocal 本身,而是因为 ThreadLocalMap 中的 key 是弱引用(WeakReference),而 value 是强引用。当 ThreadLocal 实例被回收后,key 变成 null,但 value 仍被 map 持有,且该 map 生命周期与线程一致——在线程长期存活(如线程池中的 worker 线程)时,value 就成了“不可达却无法回收”的对象。
remove(),尤其在线程复用场景(如 Servlet 容器、自定义线程池)set(null),它只是把 value 设为 null,entry 依然存在;remove() 才真正清理整个 entryRequestContextHolder、MyBatis 的 SqlSessionManager 都在请求结束时自动调用 remove(),这是最佳实践参照子线程默认**不继承**父线程的 ThreadLocal 值。如果需要父子线程间传递上下文(比如异步日志 traceId),得用 InheritableThreadLocal,但它只在 new Thread() 时复制一次,对线程池(ThreadPoolExecutor)无效——因为线程池复用线程,不会触发继承逻辑。
Runnable 或使用框架工具(如 Alibaba 的 TransmittableThreadLocal)InheritableThreadLocal 的 childValue() 方法可用于定制继承值(例如克隆对象而非引用)