remove()是防止业务数据污染的关键动作,因线程复用导致threadlocal残留值被后续请求误读,引发权限越界、数据错乱等问题,须在逻辑出口及时清理。

在使用线程池的场景下,remove() 不只是防内存泄漏的手段,更是防止业务数据污染的关键动作。
为什么线程池里不 remove 就会污染业务?
线程池中的线程被反复复用,前一个请求存入 ThreadLocal 的值(比如用户 ID、租户编码、数据库连接)不会自动消失。下一个请求若没清理就直接 get(),很可能拿到上一个用户的上下文——轻则日志错乱、权限越界,重则修改他人数据。
- Tomcat 默认线程池中,
http-nio-8080-exec-5可能今天处理张三的订单,明天处理李四的退款 - Spring 的
@Async方法、定时任务@Scheduled、自定义线程池都共享同一批线程 - 一旦某个分支漏掉 remove,残留值会在后续多次请求中“幽灵复现”,极难定位
哪些值最容易引发污染?
不是所有 ThreadLocal 都危险,但以下几类必须严格 remove:
- 用户身份信息(
userId、authToken、tenantId) - 事务/数据库上下文(如 MyBatis 的
SqlSession或自定义连接句柄) - 格式化器或构建器(
SimpleDateFormat、StringBuilder、JSONWriter)——不仅污染,还可能因状态残留导致序列化错误 - 链路追踪 ID(
traceId、spanId),影响全链路监控准确性
remove() 放在哪才真正起效?
别只图结构工整而塞进 finally 最末尾。重点是「逻辑出口」——即该变量确认不再被任何后续代码访问的位置。
- 在 Filter 中:preHandle 设置,afterCompletion 立即 remove(不要等到 doFilter chain 结束)
- 在拦截器里:避免写在 postHandle(它不保证执行),改用 afterCompletion
- 在 @Async 方法内:set 后做完核心逻辑,立刻 remove;别等方法返回再清
- 跨方法传递时:子方法用完即清,不要指望父方法统一兜底
比 remove 更进一步:用 TransmittableThreadLocal 防跨线程污染
普通 ThreadLocal 在线程切换(如 submit 到线程池、CompletableFuture 异步)后失效。若业务必须传递上下文,又不想手动 set/remove,推荐:
- 引入
com.alibaba:transmittable-thread-local - 替换
ThreadLocal<t></t>为TtlThreadLocal<t></t> - 配合
TtlExecutors.getTtlExecutorService()包装线程池,自动透传 + 自动清理
它不是替代 remove,而是把「传递 + 清理」变成可配置、可托管的行为,大幅降低出错概率。










