当前位置: 首页 > news >正文

TransmittableThreadLocal实现上下文传递-笔记

1.TransmittableThreadLocal简介

com.alibaba.ttl.TransmittableThreadLocal(简称 TTL)是阿里巴巴开源的一个工具类,旨在解决 ThreadLocal 在线程池中无法传递上下文变量 的问题。它是对 InheritableThreadLocal 的增强,尤其适用于异步编程、线程池任务调度等场景。


1.1 核心功能

  1. 上下文传递:在普通线程池中,ThreadLocal 的值不会自动传递到新线程。TransmittableThreadLocal 通过 任务包装机制,将主线程的上下文变量传递到子线程(如线程池中的线程)中。

  2. 兼容性:它兼容 ThreadLocal 和 InheritableThreadLocal 的行为,同时支持 Java 8+ 的异步编程模型(如 CompletableFutureForkJoinPool 等)。

  3. 生命周期管理:支持在任务执行前后自动绑定/解绑上下文,避免内存泄漏。


1.2 典型使用场景

  • 分布式追踪(如 SkyWalking、Zipkin):在异步调用链中传递 Trace ID。
  • 日志上下文(如 MDC):在异步任务中保留请求标识(如用户 ID、请求 ID)。
  • 事务传播:在线程池中传递事务上下文(如 Seata 分布式事务)。
  • 权限验证:在异步任务中传递用户身份信息。

2.TransmittableThreadLocal基本用法

step1.引入依赖

<!-- Maven -->
<dependency><groupId>com.alibaba</groupId><artifactId>transmittable-thread-local</artifactId><version>2.12.1</version> <!-- 使用最新版本 -->
</dependency>

step2. 定义 TransmittableThreadLocal

import com.alibaba.ttl.TransmittableThreadLocal;public class Context {private static final TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();public static void set(String value) {CONTEXT.set(value);}public static String get() {return CONTEXT.get();}
}

step3. 各种用法

import com.alibaba.ttl.TtlRunnable;ExecutorService executor = Executors.newFixedThreadPool(2);
// 主线程设置上下文
Context.set("Hello TTL");// 提交任务前包装 Runnable
// TtlRunnable 是 Runnable 的任务包装器
executor.submit(TtlRunnable.get(() -> {System.out.println("子线程: " + Context.get());
}, true)); // 第二个参数表示是否复制上下文//使用 TtlCallable 包装 Future 任务
// TtlCallable 是 Callable 的任务包装器
Future<String> future = executor.submit(TtlCallable.get(() -> {return "Result with context: " + Context.get();
}));//使用于 CompletableFuture
CompletableFuture.runAsync(TtlRunnable.get(() -> {System.out.println("CompletableFuture: " + Context.get());
}, true), executor);//如果使用自定义线程池,建议通过 TtlExecutors 包装:
Executor ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));

关键特性

1. 自动传播:如果线程池本身是 TTL 兼容的(如使用 TtlExecutors 包装),则无需手动包装任务。

2. 线程池改造:如果使用自定义线程池,建议通过 TtlExecutors 包装:

ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

3. 清除机制:需要显式调用 remove() 避免内存泄漏:

try {Context.set("value");// 执行任务
} finally {Context.remove();
}

3. TransmittableThreadLocal实现上下文传递-完整demo

3.1 场景说明:

有一个“查询用户成绩”场景,需要将用户上下文(如用户ID、租户信息、AccessKey、Locale)在 Controller、Service 层甚至异步调用中传递。

3.2 实现目标

  • 用户请求进入系统后,从请求头中提取上下文信息。
  • 上下文信息在整个请求链路中自动传递,包括异步调用。
  • 上下文信息不会被多个请求交叉污染。
  • 上下文信息在请求结束后自动清理。

3.3 代码

step1. 定义用户上下文(UserContext)

import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.Builder;
import lombok.Data;@Data
@Builder
public class UserContext {private static final TransmittableThreadLocal<UserContext> USER_CONTEXT= new TransmittableThreadLocal<UserContext>(){@Overrideprotected UserContext initialValue() {return new UserContext();}};/*** 用户ID*/private String userId;/*** 租户ID*/private String tenantId;/*** 访问密钥*/private String accessKey;/*** 语言*/private String locale;public UserContext() {}public UserContext(String userId, String tenantId, String accessKey, String locale) {this.userId = userId;this.tenantId = tenantId;this.accessKey = accessKey;this.locale = locale;}public static UserContext get() {return USER_CONTEXT.get();}public static void set(UserContext userContext) {USER_CONTEXT.set(userContext);}public static void remove() {USER_CONTEXT.remove();}}

step2. 配置拦截器(注入上下文UserContext)

定义一个UserContextInterceptor拦截器,用于从request Header中获取用户信息:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;public class UserContextInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 从请求头中提取用户信息(示例)String userId = request.getHeader("Custom-User-Id");String tenantId = request.getHeader("Custom-Tenant-Id");String accessKey = request.getHeader("Custom-Access-Key");String locale = request.getHeader("Custom-Accept-Language");// 设置用户信息UserContext userContext = UserContext.builder().userId(userId).tenantId(tenantId).accessKey(accessKey).locale(locale).build();UserContext.set(userContext);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {// 务必在请求结束时清理上下文,避免内存泄漏UserContext.remove();}
}

注册UserContextInterceptor拦截器:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class UserContextWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserContextInterceptor()).addPathPatterns("/**");}
}

step3.在 Service 层使用上下文(UserContext)

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.stereotype.Service;@Service
public class ScoreService {//配置异步线程池(使用 TtlExecutors),用于测试多线程场景下,子线程也能获取到主线程的上下文信息private Executor taskExecutor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));public void queryScore() {String userId = UserContext.get().getUserId();String tenantId = UserContext.get().getTenantId();String accessKey = UserContext.get().getAccessKey();String locale = UserContext.get().getLocale();System.out.println("Sync start ====== ");System.out.println("current thead: " + Thread.currentThread().getName());System.out.println("Sync: Querying score for user: " + userId + ", tenant: " + tenantId +" accessKey: " + accessKey + ", locale: " + locale);}//多线程场景下,子线程也能获取到主线程的上下文信息public void queryScoreAsync() {taskExecutor.execute(() -> {String userId = UserContext.get().getUserId();String tenantId = UserContext.get().getTenantId();String accessKey = UserContext.get().getAccessKey();String locale = UserContext.get().getLocale();System.out.println("ASync start ====== ");System.out.println("current thead: " + Thread.currentThread().getName());System.out.println("Async: Querying score for user: " + userId + ", tenant: " + tenantId +" accessKey: " + accessKey + ", locale: " + locale);});}
}

step4. Controller层

@RestController
public class ScoreController {@Autowiredprivate ScoreService scoreService;@GetMapping("ScoreController/queryScore")public String queryScore() {scoreService.queryScore();scoreService.queryScoreAsync();return "ScoreController queryScore success!";}@GetMapping("ScoreController/hello")public String hello() {return "ScoreController say hello";}
}

step5.测试

启动应用后,在postman中做测试。

case1: 不设置Header,UserContext获取的值为null:

case2: 设置Header,UserContext获取的值为设置的值:

case3:使用 ThreadLocal 做对比测试,新建的子线程中无法获取到UserContext设置的值

将UserContext.USER_CONTEXT设置为 ThreadLocal:

@Data
@Builder
public class UserContext {//private static final TransmittableThreadLocal<UserContext> USER_CONTEXT//    = new TransmittableThreadLocal<UserContext>(){//    @Override//    protected UserContext initialValue() {//        return new UserContext();//    }//};// 改用 ThreadLocal,其他代码不变private static final ThreadLocal<UserContext> USER_CONTEXT = new ThreadLocal<UserContext>(){@Overrideprotected UserContext initialValue() {return new UserContext();}};
}

设置Header后,发送请求,新建的子线程中无法获取到UserContext设置的值:

http://www.dtcms.com/a/200717.html

相关文章:

  • 活学妙用——5W2H分析法
  • 深入理解 Redisson 看门狗机制:保障分布式锁自动续期
  • 【工具变量】A股上市公司企业大数据运用数据(2007-2023年)
  • HTTP由浅入深
  • 力扣303 区域和检索 - 数组不可变
  • ubuntu 20.04 运行和编译LOAM_Velodyne
  • EtherCAT通讯框架
  • 导轨固定螺栓的扭矩标准是多少?
  • RAC共享存储扩容
  • win11下,启动springboot时,提示端口被占用的处理方式
  • RAG策略
  • Python读取和处理TIFF数据教程 (由简入深)
  • NumPy 2.x 完全指南【十一】N 维数组对象(ndarray)
  • 庐山派 HDMI Python 图像开发 K230图像传输方案
  • 在资源受限环境下,移动端如何实现流畅动画?如何在内存、CPU、GPU、网络等多种限制条件下,依然保持动画高帧率、低延迟、不卡顿?
  • 自定义协议与序列化
  • 大型商用货运无人机强势入局2025深圳eVTOL展
  • 【硬核数学】2. AI如何“学习”?微积分揭秘模型优化的奥秘《从零构建机器学习、深度学习到LLM的数学认知》
  • 亚远景-ASPICE与ISO 21434在汽车电子系统开发中的应用案例
  • 微服务项目->在线oj系统(Java版 - 5)
  • 机器学习第十六讲:K-means → 自动把超市顾客分成不同消费群体
  • Java—— File详解
  • VTK|箱体切割器
  • 【git进阶】git rebase(变基)
  • 前端子项目打包集成主项目实战指南
  • SOC-ESP32S3部分:1、ESP32开发IDF-5.4环境搭建
  • RV1126多线程获取SMARTP的GOP模式数据和普通GOP模式数据
  • 第二届parloo杯的RSA_Quartic_Quandary
  • 系统架构设计师考前冲刺笔记-第3章-软件架构设计
  • Pod 节点数量