ThreadLocal 想必大家都比较熟悉了,经常被大家称作线程本地变量或者线程本地存储,每个线程内部都会有一个该变量的副本,可以在线程内部任何地方使用。在项目开发过程中经常会有一些使用场景,比如将某个变量(访问IP、requestId等)绑定到一个线程上,以便随时使用,这些变量一般会有一个共同的特点:业务无关、运维需要。还有一些工具组件中也经常用到ThreadLocal,比如日志框架中MDC、调用链ID的上下文传递、数据库读写分离组件等。
ThreadLocal的问题描述
经常使用ThreadLocal的同学都知道,它是非常方便的,比如有个参数在很多处理逻辑中都会用到,但是又不想在每个函数的参数列表中写上它,原因可能是该参数本身业务无关,在函数中来回传递,感觉怪怪的。
使用ThreadLocal的时候经常会遇到两个问题:
- 创建多线程异步执行业务逻辑时,该ThreadLocal变量并不能传递到子线程中;
- 当解决了第一个问题之后,在使用线程池的时候,Java中有的线程池存在线程复用的情况,这时候ThreadLocal变量也被复用了,显然这是不符合预期;
解决问题
解决第一个问题,即父子线程中传递ThreadLocal变量。可以使用InheritableThreadLocal
替代ThreadLocal
定义变量,即可解决问题。这是Java自带的一个类,无需引入任何第三方代码库。
要解决第二个问题,即线程池中线程复用所导致的ThreadLocal变量错乱。解决这个问题就有些麻烦了,还好有alibaba出品的开源代码库 transmittable-thread-local 提供了简便的方法。首先定义变量要使用TransmittableThreadLocal
,然后定义的线程池需要特殊修饰,比如这样execService = TtlExecutors.getTtlExecutorService(execService);
,如果不方便修改线程池定义的代码,或者不能保证修改完全,alibaba还提供了另外一种方式来修饰线程池,就是Java agent的方式,只需要在Java进程的启动命令中添加-javaagent:/Users/xxx/.m2/repository/com/alibaba/transmittable-thread-local/2.10.2/transmittable-thread-local-2.10.2.jar
即可实现修饰所有线程池的目的。
实现原理
具体实现原理可以参考官方文档,和这个issue:https://github.com/alibaba/transmittable-thread-local/issues/123,这里我就不介绍了。