什么是 TransmittableThreadLocal?
TransmittableThreadLocal
(TTL)是一种扩展自 ThreadLocal
的机制,它允许在不同线程或进程之间传递变量。这种机制特别适用于那些需要在线程之间共享数据的场景,如线程池、异步任务处理、微服务架构等。
TransmittableThreadLocal 的核心优势
1. 跨线程传递
传统的 ThreadLocal
变量只能在创建它的线程内部访问,而 TransmittableThreadLocal
可以跨越线程边界,将变量值从一个线程传递到另一个线程。
2. 跨进程传递
在分布式系统中,服务之间可能需要共享某些上下文信息。TransmittableThreadLocal
可以通过序列化和反序列化机制,实现跨进程的变量传递。
3. 减少内存泄漏风险
TransmittableThreadLocal
通常提供了更精细的控制机制,允许开发者在适当的时候清理变量,从而减少因线程复用导致的内存泄漏风险。
TransmittableThreadLocal 的工作原理
TransmittableThreadLocal
的工作原理主要依赖于它内部维护的一个 Map
结构,这个 Map
将 ThreadLocal
的值与线程ID关联起来。当任务从一个线程传递到另一个线程时,TransmittableThreadLocal
会将这些值一起传递过去。
1. 值的存储
当一个变量被存储到 TransmittableThreadLocal
中时,它会将变量的值与当前线程的ID关联起来。这样,每个线程都有一个独立的变量副本。
2. 值的传递
当任务从一个线程传递到另一个线程时,TransmittableThreadLocal
会将当前线程的 Map
中的值复制到目标线程的 Map
中。这样,目标线程就可以访问到源线程的变量值。
3. 值的清理
在任务执行完毕后,为了避免内存泄露,TransmittableThreadLocal
会清理目标线程中的变量副本。
使用 TransmittableThreadLocal
使用 TransmittableThreadLocal
通常需要以下几个步骤:
1. 创建 TransmittableThreadLocal 实例
首先,你需要创建一个 TransmittableThreadLocal
实例来存储你想要跨线程传递的变量。
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
2. 设置变量值
在任务开始之前,设置 TransmittableThreadLocal
的值。
context.set("some value");
3. 跨线程传递
将任务提交到线程池或其他并发执行机制中。TransmittableThreadLocal
会负责在线程之间传递变量值。
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(() -> {
String value = context.get();
// 使用 value 进行后续处理
});
4. 清理变量
在任务执行完毕后,清理 TransmittableThreadLocal
的值,以避免内存泄漏。
context.remove();
TransmittableThreadLocal 在实际应用中的例子
1. 微服务架构中的上下文传递
在微服务架构中,一个请求可能需要经过多个服务处理。使用 TransmittableThreadLocal
可以确保请求的上下文信息(如用户身份、会话信息等)在服务之间传递。
2. 异步任务处理
在处理异步任务时,TransmittableThreadLocal
可以确保任务的上下文信息在不同的线程中保持一致。
3. 线程池中的资源共享
在线程池中,TransmittableThreadLocal
可以用于共享那些不适合在多个线程间共享的资源,如数据库连接、文件句柄等。
TransmittableThreadLocal 实现日志全链路追踪
在并发及异步条件下,不同线程之间的线程变量无法共享,普通的手段难以实现对请求id进行全链路的日志追踪,虽然可以通过重写线程池的提交方法来强行实现线程变量的共享,但是这样扩展性不好,后续不好维护,此时可以结合 TransmittableThreadLocal
来实现线程池内共享主线程变量,来实现全链路日志追踪的效果。
1. 创建定制的 TransmittableThreadLocal 实例
此工具类中引入 TransmittableThreadLocal
实例,重写其中的 beforeExecute
、afterExecute
、initialValue
方法。
TransmittableThreadLocal<Map<String, String>> ttlMDC = new TransmittableThreadLocal<Map<String, String>>() {
/**
* 在多线程数据传递的时候,将数据复制一份给MDC
*/
@Override
protected void beforeExecute() {
final Map<String, String> mdc = get();
mdc.forEach(MDC::put);
}
@Override
protected void afterExecute() {
MDC.clear();
}
@Override
protected Map<String, String> initialValue() {
return Maps.newHashMap();
}
};
1. 创建线程池并用 TtlExecutors 包装
创建线程池并使用 TtlExecutors
进行包装,后续使用此线程池即可实现线程池内同步主线程的 Slf4j
日志信息。
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 使用ttl进行包装
return TtlExecutors.getTtlExecutorService(executor.getThreadPoolExecutor());
结论
TransmittableThreadLocal
提供了一种强大的机制,使得线程局部变量可以在不同线程甚至不同进程之间传递。这在构建复杂的并发应用程序,特别是在微服务和分布式系统中,是一个非常有用的特性。通过合理使用 TransmittableThreadLocal
,开发者可以更加灵活地管理线程间的变量传递,提高应用程序的性能和可靠性。