在 Java 中,ThreadLocal 默认仅在当前线程内共享变量,父子线程无法直接共享(子线程会创建独立的 ThreadLocalMap)。实现父子线程共享变量,核心是让子线程能访问到父线程的 ThreadLocal 数据,主流方案有以下 3 种,按推荐度排序:
一、推荐方案:使用 InheritableThreadLocal(JDK 原生支持)
这是 JDK 专门为“父子线程变量继承”设计的类,是 ThreadLocal 的子类,底层通过子线程初始化时复制父线程数据实现共享。
核心原理
1.父线程中,InheritableThreadLocal 会将变量存储到线程的 inheritableThreadLocals 成员变量中(而非 ThreadLocal 用的 threadLocals);
2.当父线程创建子线程(如 new Thread())时,JVM 会触发 Thread 类的初始化逻辑,自动检查父线程是否有 inheritableThreadLocals 数据;
3.若有,子线程会浅拷贝父线程的 inheritableThreadLocals 到自己的 inheritableThreadLocals 中,实现变量继承。
使用示例
// 1. 定义 InheritableThreadLocal
private static final ThreadLocal<String> parentChildShared = new InheritableThreadLocal<>();
public static void main(String[] args) {
// 2. 父线程设置变量
parentChildShared.set("父线程的变量");
// 3. 子线程获取变量(能直接拿到父线程的值)
new Thread(() -> {
System.out.println(parentChildShared.get()); // 输出:父线程的变量
parentChildShared.remove(); // 避免内存泄漏
}).start();
parentChildShared.remove(); // 父线程用完也需清理
}
注意点
浅拷贝限制:若存储的是对象(如 User),父子线程共享的是对象引用,一方修改对象内部属性会影响另一方;
线程池不适用:线程池中的线程是复用的,子线程初始化时仅复制一次父线程数据,后续复用线程不会重新复制(需结合方案三解决)。
二、基础方案:手动传递变量(简单场景)
若仅需在“父线程创建子线程时”传递变量,可直接通过子线程的构造函数、方法参数手动传入,无需依赖 ThreadLocal 相关类。
使用示例
public static void main(String[] args) {
// 父线程定义变量
String sharedVar = "父线程的变量";
// 子线程通过构造函数接收变量
new Thread(new ChildTask(sharedVar)).start();
}
// 子线程任务类,通过成员变量存储共享值
static class ChildTask implements Runnable {
private String sharedVar;
// 构造函数接收父线程传递的变量
public ChildTask(String sharedVar) {
this.sharedVar = sharedVar;
}
@Override
public void run() {
System.out.println(sharedVar); // 输出:父线程的变量
}
}
适用场景
父子线程关系简单,无复杂线程复用(如非线程池场景);
仅需传递少量变量,无需全局共享(避免 ThreadLocal 的内存泄漏风险)。
三、进阶方案:TransmittableThreadLocal(解决线程池复用问题)
当使用线程池(如 ThreadPoolExecutor)时,InheritableThreadLocal 会失效(线程复用导致变量不更新)。此时需使用阿里开源的 TransmittableThreadLocal(TTL),它能在线程池任务执行前,重新将父线程的变量复制到复用的子线程中。
核心原理
1.TransmittableThreadLocal 继承自 InheritableThreadLocal,额外维护了“变量传递上下文”;
2.通过 TtlRunnable/TtlCallable 包装线程池任务,在任务执行前(run() 方法调用前),自动将当前父线程的 TransmittableThreadLocal 数据复制到复用的子线程中;
3.任务执行后,自动清理子线程中的变量,避免影响后续任务。
使用步骤
1.引入依赖(Maven):
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version> <!-- 最新版本需自查 -->
</dependency>
2.代码示例:
// 1. 定义 TransmittableThreadLocal
private static final ThreadLocal<String> ttlShared = new TransmittableThreadLocal<>();
public static void main(String[] args) {
// 2. 创建线程池(复用线程)
ExecutorService executor = Executors.newFixedThreadPool(1);
// 3. 父线程设置变量(每次提交任务前可更新)
ttlShared.set("父线程的变量(线程池场景)");
// 4. 用 TtlRunnable 包装任务,确保变量传递
executor.submit(TtlRunnable.get(() -> {
System.out.println(ttlShared.get()); // 输出:父线程的变量(线程池场景)
ttlShared.remove();
}));
ttlShared.remove();
executor.shutdown();
}
适用场景
核心场景:线程池复用线程时,需动态传递父线程的 ThreadLocal 变量(如分布式追踪中的 traceId、用户登录态 userId)。
总结:方案选择
| 场景 | 推荐方案 | 优点 | 缺点 |
|---|---|---|---|
| 普通父子线程(非线程池) | InheritableThreadLocal | JDK 原生,无需额外依赖 | 线程池复用失效,浅拷贝限制 |
| 简单传递(少量变量) | 手动传递(构造函数/参数) | 无内存泄漏风险,逻辑清晰 | 不支持全局共享,代码侵入性强 |
| 线程池(动态传递) | TransmittableThreadLocal | 解决线程复用问题,支持全局共享 | 需引入第三方依赖,需包装任务 |