Java怎么实现父子线程的值传递?InheritableThreadLocal类和transmittable-thread-local类?

[复制链接]
发表于 2025-9-16 20:43:13 | 显示全部楼层 |阅读模式



媒介:在Java中利用ThreadLocal类时,怎么实现父子线程直接的值传递呢?

假设这样利用会有问题吗?
  1. public class Main {
  2.     private static final ThreadLocal<String> threadlocal =  new ThreadLocal<>();
  3.     public static void main(String[] args) {
  4.            threadlocal.set("父线程设置");
  5.         new Thread( () -> {
  6.                 System.out.println("子线程读取" + threadlocal.get());
  7.         }).start();
  8.         System.out.println();
  9.     }
  10. }
复制代码
假如直接这样写的话是获取不到父线程的值的。

怎么解决呢
这里介绍俩种方式;
InheritableThreadLocal类

利用jdk自带的,InheritableThreadLocal
  1. public class Main {
  2.     // 使用InheritableThreadLocal
  3.     private static final InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
  4.     public static void main(String[] args) {
  5.            threadLocal.set("父线程设置");
  6.         new Thread( () -> {
  7.                 System.out.println("子线程读取\t" + threadLocal.get());
  8.         }).start();
  9.         System.out.println();
  10.     }
  11. }
复制代码


利用这个类实现threadlocal后可以发现可以读取到了;


那么是怎么实现的呢?
 紧张就是这俩个机制:线程创建时的值拷贝机制 和 Thread类的特殊计划
进入源码查看,
  1. <strong>首先:InheritableThreadLocal集成了ThreadLocal类。</strong>
复制代码
  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2.   
  3.     public InheritableThreadLocal() {}
  4.     // 重写childValue方法(可自定义继承逻辑)
  5.     protected T childValue(T parentValue) {
  6.         return parentValue;
  7.     }
  8.     // // 关键方法:获取线程的inheritableThreadLocals变量
  9.     ThreadLocalMap getMap(Thread t) {
  10.        return t.inheritableThreadLocals;
  11.     }
  12.   
  13.     void createMap(Thread t, T firstValue) {
  14.         t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
  15.     }
  16. }
复制代码


这是Thread类的一些片断,截取出来了,紧张看解释的地方、在init()方法中
  1. class Thread {
  2.     // 普通ThreadLocal存储
  3.     ThreadLocal.ThreadLocalMap threadLocals;
  4.    
  5.     // 专门用于继承的ThreadLocal存储
  6.     ThreadLocal.ThreadLocalMap inheritableThreadLocals;
  7.    
  8.     // 线程初始化方法
  9.     private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
  10.         init(g, target, name, stackSize, null, true);
  11.     }
  12.    
  13. //在init方法中,使用if判断了一下父线程是不是null的,就是检查父线程的
  14. //inheritableThreadLocals,如果存在父线程则会触发拷贝的逻辑。
  15. //会调用createInheritedMap这个方法,子线程就可以获得一个新的ThreadLoaclMap对象。
  16. //这样就完成了拷贝的过程
  17. //if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
  18.             // 关键点:创建时拷贝父线程的inheritableThreadLocals
  19.             //this.inheritableThreadLocals =
  20.                // ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  21. //}
  22.     private void init(..., boolean inheritThreadLocals) {
  23.         Thread parent = currentThread();
  24.         if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
  25.             // 关键点:创建时拷贝父线程的inheritableThreadLocals
  26.             this.inheritableThreadLocals =
  27.                 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  28.         }
  29.         // ...其他初始化
  30.     }
  31. }
复制代码
createInheritedMap源码解读
  1. //调用createInheritedMap时,会将父线程的ThreadLocalMap对象传入过去
  2. //此时就会遍历map对象逐个复制entry到当前线程的ThreadLocalMap中去。
  3. static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
  4.     // 创建新Map并逐个复制Entry
  5. //创建了一个全新的ThreadLocalMap对象,而不是简单的引用传递
  6.     ThreadLocalMap map = new ThreadLocalMap();
  7.     for (Entry e : parentMap.getTable()) {
  8.         if (e != null) {
  9.             @SuppressWarnings("unchecked")
  10.             ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
  11.             if (key != null) {
  12.                 // 调用childValue方法处理继承值!
  13.                 //这里我们可以重新childValue方法实现修改父线程的值。
  14. /*
  15. ​​​​​​​InheritableThreadLocal<String> itl = new InheritableThreadLocal<String>() {
  16.     @Override
  17.     protected String childValue(String parentValue) {
  18.         return "Child_" + parentValue; // 可修改继承的值
  19.     }
  20. };
  21. */
  22.                 Object value = key.childValue(e.value);
  23.                 map.set(key, value);
  24.             }
  25.         }
  26.     }
  27.     return map;
  28. }
  29. //此后子线程读父线程的值的时候,就可以读取到拷贝的值了。
复制代码
以上就是全部的过程了。这样就通过继续的方式实现了一个可传递父子线程值的类,
同时,我们根据上面的过程分析,可以发现照旧有很多不敷的;
其一由于是通过for循环遍历方式来拷贝的,假如父线程的InheritableThreadLocal中有大量数据,会低落线程创建速率。
其二有内存走漏风险,和ThreadLocal一样必要手动remove(),否则可能导致:子线程长期持有父线程的引用,大对象无法被GC。
其三:最紧张的是在线程池环境下,由于线程的复用,而不是new Thread()这样的方式创建,从而不会触发init()方法,但是拷贝的过程在init()方法中,以是在线程复用的环境下会失效。

TransmittableThreadLocal

TransmittableThreadLocal (TTL) 是阿里巴巴开源的一个线程当地变量解决方案,它解决了 InheritableThreadLocal 在线程池环境下无法正确传递值的问题。
初始化过程,
  1. public static final TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
  2. //还需要使用TtlExecutors.getTtlExecutorService()对我们的线程池做了增强(这也是必须的搭配,否则
  3. //没法使用 TransmittableThreadLocal 特性)
  4. private static ExecutorService businessExecutors = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));
复制代码
 可以点进去看详细的源码,可以望见详细的布局
  1. //可以看见继承了java.lang.InheritableThreadLocal<T>
  2. //还实现了com.alibaba.ttl.TtlCopier<T>这个类
  3. public class TransmittableThreadLocal <T>
  4. extends java.lang.InheritableThreadLocal<T>
  5. implements com.alibaba.ttl.TtlCopier<T> {
  6. /***/
  7. }
  8. package com.alibaba.ttl;
  9. //可以看见TtlCopier是一个函数式接口
  10. @java.lang.FunctionalInterface
  11. public interface TtlCopier <T> {
  12.     T copy(T t);
  13. }
复制代码
详细是怎么实现的呢?
分为4步过程:
第一步:父线程设置值(ThreadLocal.set)
  1. TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
  2. context.set("parent-value"); // 设置父线程的值
复制代码
他的存储布局是什么样的呢?
  1. //维护了一个 全局 holder(WeakHashMap)来跟踪所有 TTL实例:
  2. static ThreadLocal<Map<TransmittableThreadLocal<Object>, ?>> holder =
  3.     new ThreadLocal<Map<TransmittableThreadLocal<Object>, ?>>() {
  4.         @Override
  5.         protected Map<TransmittableThreadLocal<Object>, ?> initialValue() {
  6.             return new WeakHashMap<>(); // 弱引用防止内存泄漏
  7.         }
  8.     };
  9. //调用set时,重写了InheritableThreadLocal的set方法,
  10. //这里的disableIgnoreNullValueSemantics && value == null语句中
  11. //disableIgnoreNullValueSemantics指的是  是否禁用空值语义 。控制 null 值的存储行为
  12. //如果允许的话,value == null 就 不走remove方法,如果不允许并且value == null就会走remove方法
  13. @Override
  14.     public final void set(T value) {
  15.         if (!disableIgnoreNullValueSemantics && value == null) {
  16.             // may set null to remove value
  17.             remove();
  18.         } else {
  19.             super.set(value);
  20.             addThisToHolder();
  21.         }
  22.     }
复制代码
第二步:提交使命时获取值
  1. //当向线程池提交任务时,TTL 会 捕获当前线程的所有 TTL 值:
  2. ExecutorService executor = Executors.newFixedThreadPool(1);
  3. executor.execute(TtlRunnable.get(() -> {
  4.     // 子线程逻辑
  5. }));
  6. //关键方法 TtlRunnable.get():
  7. public static Runnable get(Runnable runnable) {
  8.     // 1. 捕获当前线程的所有 TTL 值(快照)
  9.     Map<TransmittableThreadLocal<Object>, Object> captured = capture();
  10.     return new TtlRunnable(runnable, captured);
  11. }
  12. //capture() 源码:此时,captured 是一个 键值对快照,保存了所有 TTL 的当前值。
  13. static Map<TransmittableThreadLocal<Object>, Object> capture() {
  14.     Map<TransmittableThreadLocal<Object>, Object> captured = new HashMap<>();
  15.     // 遍历 holder 中所有 TTL 实例,拷贝它们的值
  16.     for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
  17.         captured.put(threadLocal, threadLocal.copyValue()); // 深拷贝值
  18.     }
  19.     return captured;
  20. }
复制代码
第三步:子线程实行前规复值(replay)
  1. //当线程池中的线程执行任务时,TTL 会 将捕获的值恢复到当前线程:
  2. public void run() {
  3.     Map<TransmittableThreadLocal<Object>, Object> backup = replay(captured);
  4.     try {
  5.         runnable.run(); // 执行用户任务
  6.     } finally {
  7.         restore(backup); // 恢复线程原始状态
  8.     }
  9. }
  10. //为什么需要replay呢
  11. //因为在子线程中可能会修改ThreadLocal的值,另外restore里面会主动调用remove()回收,
  12. //避免内存泄露(会删除子线程新增的TTL)
  13. //有下列两种情况:
  14. //1. 一种情况是:主线程启动了一个异步任务,此时主线程和子线程会并行,
  15. //由于父子线程的数据是隔离开的,子线程此时对TTL中的内容进行修改并不会影响到原线程的逻辑
  16. //2. 另一种情况是:线程池的拒绝策略为CallerRunsPolicy时,
  17. //那么在主线程内启动这个异步任务可能会有当前的主线程来执行,
  18. //那么线程之间的数据并不会隔离,那么如果对ThreadLocal中的数据进行了修改,
  19. //那么将会影响到程序的正常运行。
  20. //replay() 源码:
  21. //先备份当前线程的原始值(防止污染)。
  22. //清除当前线程的 TTL 值(避免旧值干扰)。
  23. //将父线程的快照值设置到当前线程。
  24. static Map<TransmittableThreadLocal<Object>, Object> replay(
  25.     Map<TransmittableThreadLocal<Object>, Object> captured) {
  26.    
  27.     // 1. 备份当前线程的原始 TTL 值
  28.     Map<TransmittableThreadLocal<Object>, Object> backup = new HashMap<>();
  29.     for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
  30.         backup.put(threadLocal, threadLocal.get());
  31.     }
  32.     // 2. 清除当前线程的所有 TTL 值
  33.     for (TransmittableThreadLocal<Object> threadLocal : captured.keySet()) {
  34.         threadLocal.superRemove(); // 调用 InheritableThreadLocal.remove
  35.     }
  36.     // 3. 将捕获的父线程值设置到当前线程
  37.     for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : captured.entrySet()) {
  38.         entry.getKey().set(entry.getValue()); // 调用 TTL.set
  39.     }
  40.     return backup; // 返回备份的原始值
  41. }
复制代码
第四步:子线程读取值(TTL.get)
  1. //在任务执行期间,子线程通过 TTL.get() 读取到的值 来自父线程的快照:
  2. executor.execute(TtlRunnable.get(() -> {
  3.     System.out.println(context.get()); // 输出 "parent-value"
  4. }));
  5. //get方法源码,重写了父类的get方法,
  6. //由于之前通过 replay() 设置了值,这里会正确返回父线程传递的值
  7. @Override
  8.     public final T get() {
  9.         T value = super.get();
  10. //判断是否允许null的存在,默认是false的。前面介绍果。
  11.         if (disableIgnoreNullValueSemantics || value != null) addThisToHolder();
  12.         return value;
  13.     }
复制代码
第五步:使命实行后清算(restore)
  1. //任务执行完成后,TTL 会 恢复线程的原始状态,避免影响后续任务:
  2. //确保线程池中的线程在执行完任务后,不会残留本次任务的 TTL 值,避免内存泄漏和值污染。
  3. static void restore(Map<TransmittableThreadLocal<Object>, Object> backup) {
  4.     // 1. 清除当前线程的所有 TTL 值
  5.     for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
  6.         threadLocal.superRemove();
  7.     }
  8.     // 2. 还原备份的原始值
  9.     for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : backup.entrySet()) {
  10.         entry.getKey().set(entry.getValue());
  11.     }
  12. }
复制代码
以上就是实现的全部过程了,此中他还包装一些类,
包装机制和值快照技能,美满解决了线程池环境下的上下文传递问题,是Java异步编程中不可或缺的工具。
  1. // 包装普通Runnable
  2. public static Runnable wrap(Runnable runnable) {
  3.     Map<TransmittableThreadLocal<Object>, Object> captured = capture();
  4.     return () -> {
  5.         Map<TransmittableThreadLocal<Object>, Object> backup = replay(captured);
  6.         try {
  7.             runnable.run();
  8.         } finally {
  9.             restore(backup);
  10.         }
  11.     };
  12. }
复制代码
在capture/replay这俩个方法中有肯定开销,由于是for循环遍向来操纵数据的。我们必要克制存储大对象。
并且TTL是存在线程安全问题的,由于默认都是引用范例拷贝,假如子线程修改了数据,主线程是可以感知到的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表