阿里开源 TransmittableThreadLocal(TTL)教程

一、简介

TransmittableThreadLocal(简称 TTL)是阿里巴巴开源的解决线程池上下文传递 问题的工具。它解决了 InheritableThreadLocal 在线程池场景下无法传递上下文的痛点。

核心问题

  • ThreadLocal:无法在父子线程间传递

  • InheritableThreadLocal:可以在创建时传递,但线程复用场景(线程池)无效

适用场景

  • 链路追踪(TraceId 传递)

  • 用户认证信息传递

  • 配置信息传递

  • 请求上下文传递


二、快速开始

1. Maven 依赖

html 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.4</version>
</dependency>

2. 基本使用

java 复制代码
import com.alibaba.ttl.TransmittableThreadLocal;

public class TTLBasicDemo {
    // 创建 TTL 实例
    private static TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
    
    public static void main(String[] args) {
        // 设置值
        ttl.set("main-value");
        
        // 普通线程能传递
        new Thread(() -> {
            System.out.println("普通线程: " + ttl.get()); // 输出: main-value
        }).start();
        
        // 线程池场景
        ExecutorService executor = Executors.newFixedThreadPool(1);
        
        // 未包装的任务无法传递
        executor.submit(() -> {
            System.out.println("未包装: " + ttl.get()); // 输出: null
        });
        
        // 包装后的任务可以传递
        executor.submit(TtlRunnable.get(() -> {
            System.out.println("TTL包装: " + ttl.get()); // 输出: main-value
        }));
        
        executor.shutdown();
    }
}

三、核心 API

1. TransmittableThreadLocal

java 复制代码
// 创建 TTL
TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();

// 基本操作
ttl.set("value");           // 设置值
String value = ttl.get();   // 获取值
ttl.remove();               // 移除值

// 初始化值
TransmittableThreadLocal<String> ttlWithInit = new TransmittableThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "default-value";
    }
};

2. 包装工具类

java 复制代码
// Runnable 包装
Runnable wrappedRunnable = TtlRunnable.get(originalRunnable);

// Callable 包装
Callable wrappedCallable = TtlCallable.get(originalCallable);

// 线程池包装
ExecutorService executor = TtlExecutors.getTtlExecutorService(originalExecutor);

四、线程池包装方式

方式一:包装任务(推荐用于临时场景)

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(5);

// 每次提交时包装
executor.submit(TtlRunnable.get(() -> {
    System.out.println(ttl.get());
}));

方式二:包装线程池(推荐用于全局配置)

java 复制代码
ExecutorService originalExecutor = Executors.newFixedThreadPool(5);
ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(originalExecutor);

// 直接提交,自动包装
ttlExecutor.submit(() -> {
    System.out.println(ttl.get()); // 自动传递
});

支持的各种线程池

java 复制代码
// 普通线程池
ExecutorService executor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));

// 定时任务线程池
ScheduledExecutorService scheduledExecutor = TtlExecutors.getTtlScheduledExecutorService(
    Executors.newScheduledThreadPool(5)
);

// ForkJoinPool
ForkJoinPool forkJoinPool = TtlExecutors.getTtlForkJoinPool(new ForkJoinPool(5));

五、实战案例

案例1:链路追踪 TraceId 传递

java 复制代码
public class TraceContext {
    private static TransmittableThreadLocal<String> traceIdHolder = new TransmittableThreadLocal<>();
    
    public static void setTraceId(String traceId) {
        traceIdHolder.set(traceId);
    }
    
    public static String getTraceId() {
        return traceIdHolder.get();
    }
    
    public static void clear() {
        traceIdHolder.remove();
    }
}

// 拦截器设置 TraceId
@Component
public class TraceInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String traceId = UUID.randomUUID().toString();
        TraceContext.setTraceId(traceId);
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) {
        TraceContext.clear();
    }
}

// 业务代码中使用
@Service
public class OrderService {
    @Autowired
    private ThreadPoolExecutor threadPool;
    
    public void processOrder() {
        String traceId = TraceContext.getTraceId();
        
        // 使用包装后的线程池,自动传递 TraceId
        threadPool.submit(() -> {
            // 这里能获取到 TraceId
            log.info("处理订单,traceId: {}", TraceContext.getTraceId());
        });
    }
}

案例2:用户上下文传递

java 复制代码
public class UserContext {
    private static TransmittableThreadLocal<UserInfo> userHolder = new TransmittableThreadLocal<>();
    
    public static void setUser(UserInfo user) {
        userHolder.set(user);
    }
    
    public static UserInfo getUser() {
        return userHolder.get();
    }
    
    public static Long getUserId() {
        UserInfo user = getUser();
        return user != null ? user.getId() : null;
    }
}

// 使用示例
@Service
public class AsyncService {
    private ExecutorService executor = TtlExecutors.getTtlExecutorService(
        Executors.newFixedThreadPool(10)
    );
    
    public void asyncProcess() {
        UserInfo currentUser = UserContext.getUser();
        
        executor.submit(() -> {
            // 自动获取当前用户上下文
            UserInfo user = UserContext.getUser();
            System.out.println("处理用户: " + user.getName());
        });
    }
}

案例3:Spring Boot 集成

java 复制代码
@Configuration
public class ThreadPoolConfig {
    
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        
        // 包装 ThreadPoolTaskExecutor
        return TtlExecutors.getTtlThreadPoolTaskExecutor(executor);
    }
    
    @Bean
    public ExecutorService customExecutor() {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        return TtlExecutors.getTtlExecutorService(executor);
    }
}

六、高级特性

1. 值拷贝机制

java 复制代码
// 实现 TransmittableThreadLocal.Transmittable 接口自定义拷贝
TransmittableThreadLocal<List<String>> ttl = new TransmittableThreadLocal<List<String>>() {
    @Override
    public List<String> copy(List<String> parentValue) {
        // 深拷贝,避免引用传递导致的数据不一致
        return parentValue != null ? new ArrayList<>(parentValue) : null;
    }
};

2. 禁用 TTL(全局配置)

java 复制代码
// 通过系统属性禁用
System.setProperty("ttl.agent.enabled", "false");

// 或通过环境变量
-Dttl.agent.enabled=false

3. 优雅关闭清理

java 复制代码
@Component
public class TtlCleanupFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        try {
            chain.doFilter(request, response);
        } finally {
            // 清理所有 TTL 值,防止内存泄漏
            TransmittableThreadLocal.Transmitter.clear();
        }
    }
}

七、性能优化建议

1. 优先包装线程池而非任务

  • 包装线程池性能更好,避免每次任务创建额外对象

2. 及时清理

java 复制代码
try {
    ttl.set(value);
    // 业务逻辑
} finally {
    ttl.remove();  // 重要:避免内存泄漏
}

3. 使用静态变量

java 复制代码
// 推荐:定义为静态变量
private static final TransmittableThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>();

// 不推荐:每次使用时创建
public void method() {
    TransmittableThreadLocal<String> local = new TransmittableThreadLocal<>();
}

八、常见问题与解决方案

1. 值未传递问题

原因:未正确包装线程池或任务

解决

java 复制代码
// 错误
executor.submit(() -> {});

// 正确
executor.submit(TtlRunnable.get(() -> {}));
// 或使用包装后的线程池

2. 内存泄漏

原因 :未调用 remove() 清理

解决

java 复制代码
try {
    ttl.set(value);
    // 业务逻辑
} finally {
    ttl.remove();  // 确保清理
}

3. 嵌套线程池问题

java 复制代码
// 外层线程池
ExecutorService outer = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));

outer.submit(() -> {
    // 内层线程池也需要包装
    ExecutorService inner = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));
    inner.submit(() -> {
        // 可以正常传递
    });
});

九、对比总结

特性 ThreadLocal InheritableThreadLocal TransmittableThreadLocal
父子线程传递
线程池传递
性能开销
内存泄漏风险 低(需手动清理)
适用场景 线程内隔离 简单父子线程 线程池/异步场景

十、最佳实践总结

  1. 优先包装线程池 :使用 TtlExecutors 包装线程池,避免遗漏任务包装

  2. 及时清理 :在 finally 块中调用 remove() 方法

  3. 静态定义:将 TTL 定义为静态常量,避免重复创建

  4. 深拷贝 :对于可变对象,实现 copy() 方法进行深拷贝

  5. 统一管理:使用拦截器/过滤器统一管理上下文的设置和清理

  6. 监控告警:监控 TTL 的内存占用,设置合理告警

相关推荐
闲人编程28 分钟前
开源 vs 闭源:构建Agent该如何选择基座模型?
ai·开源·微调·智能体·决策·自进化·决策矩阵
计算机魔术师32 分钟前
【AI Agent 工程 | 能力分级】从 L1 到 L5:MIT AI Agent Index 分级系统完全拆解
开源
星栈34 分钟前
每次改订单,我都存了快照
后端·rust·开源
亦暖筑序35 分钟前
Vibe Coding 用久了,代码手感真的会退化——以及我怎么试图解决这个问题
程序员·开源·github
太阳之子36 分钟前
开源推荐:一个专为 AI Agent 设计的求职自动化工具
开源
sbjdhjd2 小时前
01| 裸机部署 K8S:从零搭建生产可用集群
运维·经验分享·云原生·kubernetes·开源·kubelet·kubeless
一直会游泳的小猫13 小时前
gstack-guide
开源·安全防护·ai辅助开发·技能工具集·sprint流程
Hical_W14 小时前
Hical 踩坑实录五部曲(五):Boost.MySQL 协程集成的 5 个坑
数据库·mysql·开源
Hical6117 小时前
C++26 反射落地实战
c++·开源
IvorySQL17 小时前
从 repack.c 深入理解 PostgreSQL REPACK 的底层实现
数据库·postgresql·开源