🧠 InheritableThreadLocal 原理详解笔记
✅ 1. 本质区别:ThreadLocal
vs InheritableThreadLocal
-
ThreadLocal:线程本地变量,仅当前线程可访问,不会传递给子线程。
-
InheritableThreadLocal:可继承的线程本地变量,在创建子线程时会将父线程的值复制过去。
🔑 2. 子线程能访问父线程值的真正原因
当你写下下面这行代码:
private static final InheritableThreadLocal<Map<String, Object>> context = new InheritableThreadLocal<>();
你其实是声明了一个名为 context
的变量,它指向一个 InheritableThreadLocal 实例。
那么关键点来了:
💡 子线程在创建时,JVM 会把父线程的
ThreadLocalMap
中所有的InheritableThreadLocal
的值复制一份到子线程自己的ThreadLocalMap
中。
具体表现为:
-
子线程会拥有自己的
ThreadLocalMap
。 -
这个 map 的 key 是父线程的 InheritableThreadLocal 对象实例(如
context
)。 -
value 是父线程
context.get()
的值,或者childValue()
方法的返回结果(如果你覆写了它)。
💬 3. 为什么子线程可以直接用 context.get()
?
因为:
-
context
是一个变量,指向同一个InheritableThreadLocal
实例。 -
子线程调用
context.get()
,本质是在用这个InheritableThreadLocal
实例当作 key,从自己的ThreadLocalMap
中获取值。 -
这个 key(也就是
context
)在子线程启动时就被复制过来了,所以能取到。
✅ 所以变量名可以一样,是因为它本来就是同一个实例引用。不是变量名继承了,而是 key 是同一个对象。
🧬 4. 可视化理解(简化图)
假设你在父线程这样设置:
context.set(Map.of("client", xxx, "MAGIC", yyy));
子线程启动时,JVM 会自动做:
子线程的 threadLocals:
key: 父线程的 context(InheritableThreadLocal 实例)
value: Map.of("client", xxx, "MAGIC", yyy)
于是子线程调用:
context.get(); // 实际上是从自己的 threadLocals 中用 context 作为 key 取值
public class InheritableThreadLocalDemo {
// 创建一个 InheritableThreadLocal 变量
private static InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
public static void main(String[] args) {
// 父线程设置 context 的值
context.set("Parent Thread Context");
// 打印父线程的 context 值
System.out.println("Parent Thread context: " + context.get());
// 启动一个子线程
Thread childThread = new Thread(new Runnable() {
@Override
public void run() {
// 子线程访问父线程的 context 值
System.out.println("Child Thread context before change: " + context.get());
// 子线程修改自己的 context 值
context.set("Child Thread Context");
// 子线程修改后的 context 值
System.out.println("Child Thread context after change: " + context.get());
}
});
// 启动子线程
childThread.start();
// 主线程继续使用自己的 context 值
try {
childThread.join(); // 等待子线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印父线程的 context 值
System.out.println("Parent Thread context after child thread starts: " + context.get());
}
}
补充一点父子线程之间数据的传输除了这个之外:还可与使用juc提供的一些集合如:
✅ 常用的 JUC 集合类(适用于父子线程数据传输)
1. BlockingQueue 接口及其实现类
这些是最常用的线程间通信工具,尤其适合生产者-消费者模型,但也适合父子线程的数据传输。
-
ArrayBlockingQueue
-
LinkedBlockingQueue
-
PriorityBlockingQueue
-
DelayQueue
-
SynchronousQueue
示例:
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
Thread child = new Thread(() -> {
try {
String data = queue.take(); // 子线程等待父线程传数据
System.out.println("子线程收到数据:" + data);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
child.start();
queue.put("父线程传的数据"); // 父线程传数据到队列中
2. ConcurrentHashMap / ConcurrentSkipListMap
如果父子线程要共享结构化数据,比如键值对,可以使用这些线程安全的 Map:
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
Thread child = new Thread(() -> {
String value = map.get("key");
System.out.println("子线程读取到数据:" + value);
});
map.put("key", "value from parent"); // 父线程写入
child.start();
3. CopyOnWriteArrayList / CopyOnWriteArraySet
用于读多写少的场景,适合子线程读取父线程准备的数据。
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("parent data");
Thread child = new Thread(() -> {
System.out.println("子线程读取到列表:" + list);
});
child.start();
4. Exchanger
如果父子线程是一对一交换数据的场景,Exchanger
非常合适。
Exchanger<String> exchanger = new Exchanger<>();
Thread child = new Thread(() -> {
try {
String data = exchanger.exchange("子线程数据");
System.out.println("子线程收到:" + data);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
child.start();
String fromChild = exchanger.exchange("父线程数据");
System.out.println("父线程收到:" + fromChild);
总结:适用于父子线程通信的 JUC 集合
类型 | 特点 | 适用场景 |
---|---|---|
BlockingQueue 系列 | 支持阻塞的线程安全队列 | 生产者-消费者、父子线程通信 |
ConcurrentHashMap | 高并发读写的线程安全 Map | 父子线程共享结构化数据 |
CopyOnWriteArrayList | 读多写少,写操作复制 | 父写子读,数据量不大 |
Exchanger | 线程对之间互相交换数据 | 一对一数据交换 |