问题
父子线程之间怎么共享、传递数据?
答案
核心概念
父子线程指创建线程(父线程)与被创建线程(子线程)的关系。普通ThreadLocal无法在父子线程间共享数据,需要使用InheritableThreadLocal。
方案对比
| 方案 | 适用场景 | 优缺点 |
|---|---|---|
| 共享对象引用 | 简单场景 | ✅简单直接 ❌需要考虑线程安全 |
| InheritableThreadLocal | new Thread()场景 | ✅自动继承 ❌线程池场景失效 |
| TransmittableThreadLocal | 线程池场景 | ✅线程池可用 ❌需要引入外部库 |
方案一:共享对象引用
原理:直接传递共享对象给子线程,通过锁或线程安全类保证并发安全。
public class SharedDataDemo {
public static void main(String[] args) {
// 使用线程安全的容器
ConcurrentHashMap<String, String> sharedMap = new ConcurrentHashMap<>();
sharedMap.put("userId", "10001");
// 父线程
Thread parentThread = Thread.currentThread();
System.out.println("父线程: " + sharedMap.get("userId"));
// 子线程(传递引用)
new Thread(() -> {
System.out.println("子线程读取: " + sharedMap.get("userId"));
sharedMap.put("userId", "10002"); // 修改共享数据
}).start();
// 等待子线程执行
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("父线程读取修改后的值: " + sharedMap.get("userId"));
}
}
// 输出:
// 父线程: 10001
// 子线程读取: 10001
// 父线程读取修改后的值: 10002
缺点:
- 需要显式传递对象引用
- 必须考虑线程安全(使用锁或并发容器)
- 不适合复杂的上下文传递
方案二:InheritableThreadLocal(推荐)
原理:子线程创建时会自动复制父线程的InheritableThreadLocal变量。
基本使用
public class InheritableThreadLocalDemo {
// 使用InheritableThreadLocal替代ThreadLocal
private static InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 父线程设置值
context.set("父线程的数据");
System.out.println("父线程: " + context.get());
// 创建子线程
Thread childThread = new Thread(() -> {
System.out.println("子线程读取: " + context.get()); // 自动继承父线程的值
// 子线程修改不影响父线程
context.set("子线程修改后的数据");
System.out.println("子线程修改后: " + context.get());
});
childThread.start();
childThread.join();
System.out.println("父线程值未变: " + context.get());
}
}
// 输出:
// 父线程: 父线程的数据
// 子线程读取: 父线程的数据
// 子线程修改后: 子线程修改后的数据
// 父线程值未变: 父线程的数据
实现原理
核心源码分析:
public class Thread {
// 普通ThreadLocal存储在这里
ThreadLocal.ThreadLocalMap threadLocals = null;
// InheritableThreadLocal存储在这里
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// 重写getMap方法,返回inheritableThreadLocals而非threadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
// 子类可重写此方法自定义继承逻辑(默认直接复制引用)
protected T childValue(T parentValue) {
return parentValue;
}
}
线程初始化时的继承逻辑:
// Thread构造方法(简化版)
public Thread(Runnable target) {
Thread parent = currentThread(); // 获取父线程
// 如果父线程有inheritableThreadLocals,则复制给子线程
if (parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
// ThreadLocal.createInheritedMap方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap); // 复制父线程的Map
}
// ThreadLocalMap的复制构造方法
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 调用childValue方法(可自定义)
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
table[...] = c; // 存入子线程的Map
}
}
}
}
继承时机:
- 在
new Thread()时触发,子线程创建时复制父线程的值 - 父线程后续修改不影响已创建的子线程
自定义继承逻辑
场景:需要深拷贝对象,避免父子线程共享同一对象引用。
public class CustomInheritableThreadLocal<T> extends InheritableThreadLocal<T> {
@Override
protected T childValue(T parentValue) {
// 深拷贝(示例:序列化方式)
if (parentValue instanceof Serializable) {
return deepCopy(parentValue);
}
return parentValue;
}
private T deepCopy(T obj) {
// 使用序列化实现深拷贝(生产环境建议使用专业库)
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// 使用
CustomInheritableThreadLocal<User> userContext = new CustomInheritableThreadLocal<>();
userContext.set(new User("张三", 25));
new Thread(() -> {
User user = userContext.get(); // 获取的是深拷贝对象
user.setAge(30); // 修改不影响父线程
}).start();
线程池场景的问题
核心问题:InheritableThreadLocal只在线程创建时复制,线程池中的线程会被复用,导致值不更新。
public class ThreadPoolProblem {
private static InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
// 第一次任务
context.set("任务1的上下文");
executor.execute(() -> {
System.out.println("任务1读取: " + context.get()); // 输出: 任务1的上下文
});
Thread.sleep(100);
// 第二次任务(复用同一个线程)
context.set("任务2的上下文");
executor.execute(() -> {
System.out.println("任务2读取: " + context.get()); // 输出: 任务1的上下文(错误!)
});
executor.shutdown();
}
}
原因:
- 线程池中的线程在第一次创建时复制了”任务1的上下文”
- 线程被复用执行任务2时,不会再次复制
- 导致任务2读取到的是旧值
解决方案:使用TransmittableThreadLocal(阿里开源)
// 引入依赖
// <dependency>
// <groupId>com.alibaba</groupId>
// <artifactId>transmittable-thread-local</artifactId>
// </dependency>
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// 包装线程池
ExecutorService executor = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(10)
);
// 或使用装饰器包装Runnable
executor.execute(TtlRunnable.get(() -> {
System.out.println(context.get()); // 正确获取当前任务的上下文
}));
实际应用场景
1. 分布式追踪TraceId传递:
public class TraceContext {
private static InheritableThreadLocal<String> traceId = new InheritableThreadLocal<>();
public static void setTraceId(String id) {
traceId.set(id);
}
public static String getTraceId() {
return traceId.get();
}
}
// 主线程
TraceContext.setTraceId("TRACE-12345");
logService.log("主线程操作"); // [TRACE-12345] 主线程操作
// 子线程自动继承TraceId
new Thread(() -> {
logService.log("子线程操作"); // [TRACE-12345] 子线程操作
}).start();
2. 用户上下文传递:
InheritableThreadLocal<User> userContext = new InheritableThreadLocal<>();
// 主线程
userContext.set(currentUser);
// 异步任务中访问用户信息
new Thread(() -> {
User user = userContext.get(); // 自动获取父线程的用户信息
orderService.createOrder(user.getId());
}).start();
注意事项
| 注意点 | 说明 |
|---|---|
| 值的复制时机 | 子线程创建时复制,父线程后续修改不影响子线程 |
| 默认浅拷贝 | 父子线程共享同一对象引用,需要重写childValue实现深拷贝 |
| 线程池场景失效 | 线程复用导致不会重新复制,需使用TransmittableThreadLocal |
| 内存泄漏风险 | 同ThreadLocal,必须调用remove()清理 |
| 不适合频繁创建线程 | 每次创建线程都会复制Map,有一定开销 |
面试答题要点
- 核心机制:InheritableThreadLocal在子线程创建时自动复制父线程的值
- 实现原理:Thread对象维护inheritableThreadLocals字段,构造方法中复制父线程的Map
- 线程池问题:线程复用导致不会重新复制,需使用TransmittableThreadLocal
- 适用场景:分布式追踪TraceId传递、用户上下文传递、权限信息传递
- 最佳实践:new Thread()场景用InheritableThreadLocal,线程池场景用TransmittableThreadLocal