

简介
Java的ThreadLocal作为隐式传参和线程安全的利器,在工程中被大量使用。回顾之前博文介绍的避坑系列(博文底部有推荐阅读),ThreadLocal经常导致信息丢失、信息错乱或OOM等问题,其根本原因是ThreadLocal里保存的信息没有很好的被初始化和清理。
那么如何方便快捷的统一处理这些问题?
如何统一处理ThreadLocal的这些问题?
目前基本有两个大的解决方向:
1、java agent + 字节码修改 ,使用JVM级别的AOP 边车模式统一处理。
非业务相关的功能都可以采用java agent模式处理,比如应用广泛的链路跟踪skywalking。
2、项目中必须显示或隐式采用代理模式的方式,使用封装好的线程池工具。
比如显示使用Spring实现的链路信息传递线程池
org.springframework.cloud.sleuth.instrument.async.TraceableExecutorService或者被spring容器管理的线程池经过BeanPostProcessor
org.springframework.cloud.sleuth.autoconfig.instrument.async.ExecutorBeanPostProcessor自动代理为链路传递的线程池。
那接下来如何把项目中使用的所涉及到的ThreadLocal实例统一透传信息呢?比如jdk中的线程池我们可以重写方法beforeExecute、afterExecute方法初始化和清理ThreadLocal,不过Spring中的链路信息线程池实现了
org.springframework.cloud.sleuth.instrument.async.TraceRunnable
org.springframework.cloud.sleuth.instrument.async.TraceCallable以初始化和清理对应的链路信息。
假如我们按照Spring中的链路信息线程池的思路来统一封装ThreadLocal信息传递,由于不同的场景和框架的引入,ThreadLocal实例也很多,我们不可能每次加一个ThreadLocal实例信息的传递,都要更改动实现逻辑,所以必须抽象出接口的形式统一处理。

ThreadLocal实例信息的传递必须实现上述接口,而且必须使用下面的工具注册:

ThreadLocalCopyUtils管理所有的ThreadLocal实例,在自己实现的
ThreadLocalRunnable,实现ThreadLocal实例信息的透传与清理工作:

实现ThreadLocal信息透传的自定义线程池如下:

主要逻辑是原Runnable逻辑变为我们实现的ThreadLocalRunnable。
测试一下:

当测试的TEST、MDC没有被托管之前,即:
ThreadLocalCopyUtils.register(new MDCThreadLocalCopy());
ThreadLocalCopyUtils.register(new TestThreadLocalCopy());运行结果:

主线中的信息在异步线程中丢失,当测试的TEST、MDC被托管之后,运行结果:

主线程中的信息被传递到异步线程中。
当我们要把其他涉及到业务信息的ThreadLocal实例透传时,我们只需要实现接口AbstractThreadLocalCopy,实现其信息透传逻辑,并调用方法:
com.renzhikeji.demo.threadlocal.ThreadLocalCopyUtils#register被我们托管,搭配上我们封装的线程池,就可以非常方便的防止ThreadLocal信息丢失及清理问题。
小结
本文就如何方便快捷的解决ThreadLocal信息丢失、信息错乱及OOM问题,介绍了项目实践中经常被使用的两个主要的实现方式。