当前位置: 首页 > news >正文

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

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

假设这样使用会有问题吗?

public class Main {private static final ThreadLocal<String> threadlocal =  new ThreadLocal<>();public static void main(String[] args) {threadlocal.set("父线程设置");new Thread( () -> {System.out.println("子线程读取" + threadlocal.get());}).start();System.out.println();}
}

如果直接这样写的话是获取不到父线程的值的。

怎么解决呢

这里介绍俩种方式;

InheritableThreadLocal类

使用jdk自带的,InheritableThreadLocal

public class Main {// 使用InheritableThreadLocalprivate static final InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {threadLocal.set("父线程设置");new Thread( () -> {System.out.println("子线程读取\t" + threadLocal.get());}).start();System.out.println();}
}

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

那么是怎么实现的呢?

 主要就是这俩个机制:线程创建时的值拷贝机制 和 Thread类的特殊设计

进入源码查看,

首先:InheritableThreadLocal集成了ThreadLocal类。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {public InheritableThreadLocal() {}// 重写childValue方法(可自定义继承逻辑)protected T childValue(T parentValue) {return parentValue;}// // 关键方法:获取线程的inheritableThreadLocals变量ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

这是Thread类的一些片段,截取出来了,主要看注释的地方、在init()方法中


class Thread {// 普通ThreadLocal存储ThreadLocal.ThreadLocalMap threadLocals;// 专门用于继承的ThreadLocal存储ThreadLocal.ThreadLocalMap inheritableThreadLocals;// 线程初始化方法private void init(ThreadGroup g, Runnable target, String name, long stackSize) {init(g, target, name, stackSize, null, true);}//在init方法中,使用if判断了一下父线程是不是null的,就是检查父线程的
//inheritableThreadLocals,如果存在父线程则会触发拷贝的逻辑。
//会调用createInheritedMap这个方法,子线程就可以获得一个新的ThreadLoaclMap对象。
//这样就完成了拷贝的过程
//if (inheritThreadLocals && parent.inheritableThreadLocals != null) {// 关键点:创建时拷贝父线程的inheritableThreadLocals//this.inheritableThreadLocals = // ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//}private void init(..., boolean inheritThreadLocals) {Thread parent = currentThread();if (inheritThreadLocals && parent.inheritableThreadLocals != null) {// 关键点:创建时拷贝父线程的inheritableThreadLocalsthis.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);}// ...其他初始化}
}

createInheritedMap源码解读

//调用createInheritedMap时,会将父线程的ThreadLocalMap对象传入过去
//此时就会遍历map对象逐个复制entry到当前线程的ThreadLocalMap中去。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {// 创建新Map并逐个复制Entry
//创建了一个全新的ThreadLocalMap对象,而不是简单的引用传递ThreadLocalMap map = new ThreadLocalMap();for (Entry e : parentMap.getTable()) {if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {// 调用childValue方法处理继承值!//这里我们可以重新childValue方法实现修改父线程的值。
/*
​​​​​​​InheritableThreadLocal<String> itl = new InheritableThreadLocal<String>() {@Overrideprotected String childValue(String parentValue) {return "Child_" + parentValue; // 可修改继承的值}
};*/Object value = key.childValue(e.value);map.set(key, value);}}}return map;
}
//此后子线程读父线程的值的时候,就可以读取到拷贝的值了。

以上就是全部的过程了。这样就通过继承的方式实现了一个可传递父子线程值的类,

同时,我们根据上面的过程分析,可以发现还是有很多不足的;

其一因为是通过for循环遍历方式来拷贝的,如果父线程的InheritableThreadLocal中有大量数据,会降低线程创建速度。

其二有内存泄漏风险,和ThreadLocal一样需要手动remove(),否则可能导致:子线程长期持有父线程的引用,大对象无法被GC。

其三:最重要的是在线程池环境下,因为线程的复用,而不是new Thread()这样的方式创建,从而不会触发init()方法,但是拷贝的过程在init()方法中,所以在线程复用的情况下会失效。

TransmittableThreadLocal

TransmittableThreadLocal (TTL) 是阿里巴巴开源的一个线程本地变量解决方案,它解决了 InheritableThreadLocal 在线程池环境下无法正确传递值的问题。

初始化过程,

public static final TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();//还需要使用TtlExecutors.getTtlExecutorService()对我们的线程池做了增强(这也是必须的搭配,否则
//没法使用 TransmittableThreadLocal 特性)private static ExecutorService businessExecutors = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));

 可以点进去看具体的源码,可以看见具体的结构

//可以看见继承了java.lang.InheritableThreadLocal<T> 
//还实现了com.alibaba.ttl.TtlCopier<T>这个类
public class TransmittableThreadLocal <T> 
extends java.lang.InheritableThreadLocal<T> 
implements com.alibaba.ttl.TtlCopier<T> {/***/}package com.alibaba.ttl;
//可以看见TtlCopier是一个函数式接口
@java.lang.FunctionalInterface
public interface TtlCopier <T> {T copy(T t);
}

具体是怎么实现的呢?

分为4步过程:

第一步:父线程设置值(ThreadLocal.set)

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("parent-value"); // 设置父线程的值

他的存储结构是什么样的呢?

//维护了一个 全局 holder(WeakHashMap)来跟踪所有 TTL实例:static ThreadLocal<Map<TransmittableThreadLocal<Object>, ?>> holder = new ThreadLocal<Map<TransmittableThreadLocal<Object>, ?>>() {@Overrideprotected Map<TransmittableThreadLocal<Object>, ?> initialValue() {return new WeakHashMap<>(); // 弱引用防止内存泄漏}};//调用set时,重写了InheritableThreadLocal的set方法,
//这里的disableIgnoreNullValueSemantics && value == null语句中
//disableIgnoreNullValueSemantics指的是  是否禁用空值语义 。控制 null 值的存储行为
//如果允许的话,value == null 就 不走remove方法,如果不允许并且value == null就会走remove方法
@Overridepublic final void set(T value) {if (!disableIgnoreNullValueSemantics && value == null) {// may set null to remove valueremove();} else {super.set(value);addThisToHolder();}}

第二步:提交任务时获取值


//当向线程池提交任务时,TTL 会 捕获当前线程的所有 TTL 值:
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.execute(TtlRunnable.get(() -> {// 子线程逻辑
}));//关键方法 TtlRunnable.get():
public static Runnable get(Runnable runnable) {// 1. 捕获当前线程的所有 TTL 值(快照)Map<TransmittableThreadLocal<Object>, Object> captured = capture();return new TtlRunnable(runnable, captured);
}//capture() 源码:此时,captured 是一个 键值对快照,保存了所有 TTL 的当前值。
static Map<TransmittableThreadLocal<Object>, Object> capture() {Map<TransmittableThreadLocal<Object>, Object> captured = new HashMap<>();// 遍历 holder 中所有 TTL 实例,拷贝它们的值for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {captured.put(threadLocal, threadLocal.copyValue()); // 深拷贝值}return captured;
}

第三步:子线程执行前恢复值(replay)


//当线程池中的线程执行任务时,TTL 会 将捕获的值恢复到当前线程:
public void run() {Map<TransmittableThreadLocal<Object>, Object> backup = replay(captured);try {runnable.run(); // 执行用户任务} finally {restore(backup); // 恢复线程原始状态}
}
//为什么需要replay呢
//因为在子线程中可能会修改ThreadLocal的值,另外restore里面会主动调用remove()回收,
//避免内存泄露(会删除子线程新增的TTL)
//有下列两种情况:
//1. 一种情况是:主线程启动了一个异步任务,此时主线程和子线程会并行,
//由于父子线程的数据是隔离开的,子线程此时对TTL中的内容进行修改并不会影响到原线程的逻辑
//2. 另一种情况是:线程池的拒绝策略为CallerRunsPolicy时,
//那么在主线程内启动这个异步任务可能会有当前的主线程来执行,
//那么线程之间的数据并不会隔离,那么如果对ThreadLocal中的数据进行了修改,
//那么将会影响到程序的正常运行。
//replay() 源码://先备份当前线程的原始值(防止污染)。//清除当前线程的 TTL 值(避免旧值干扰)。//将父线程的快照值设置到当前线程。static Map<TransmittableThreadLocal<Object>, Object> replay(Map<TransmittableThreadLocal<Object>, Object> captured) {// 1. 备份当前线程的原始 TTL 值Map<TransmittableThreadLocal<Object>, Object> backup = new HashMap<>();for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {backup.put(threadLocal, threadLocal.get());}// 2. 清除当前线程的所有 TTL 值for (TransmittableThreadLocal<Object> threadLocal : captured.keySet()) {threadLocal.superRemove(); // 调用 InheritableThreadLocal.remove}// 3. 将捕获的父线程值设置到当前线程for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : captured.entrySet()) {entry.getKey().set(entry.getValue()); // 调用 TTL.set}return backup; // 返回备份的原始值
}

第四步:子线程读取值(TTL.get)

//在任务执行期间,子线程通过 TTL.get() 读取到的值 来自父线程的快照:
executor.execute(TtlRunnable.get(() -> {System.out.println(context.get()); // 输出 "parent-value"
}));//get方法源码,重写了父类的get方法,
//由于之前通过 replay() 设置了值,这里会正确返回父线程传递的值
@Overridepublic final T get() {T value = super.get();
//判断是否允许null的存在,默认是false的。前面介绍果。if (disableIgnoreNullValueSemantics || value != null) addThisToHolder();return value;}

第五步:任务执行后清理(restore)

//任务执行完成后,TTL 会 恢复线程的原始状态,避免影响后续任务:
//确保线程池中的线程在执行完任务后,不会残留本次任务的 TTL 值,避免内存泄漏和值污染。
static void restore(Map<TransmittableThreadLocal<Object>, Object> backup) {// 1. 清除当前线程的所有 TTL 值for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {threadLocal.superRemove();}// 2. 还原备份的原始值for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : backup.entrySet()) {entry.getKey().set(entry.getValue());}
}

以上就是实现的全部过程了,其中他还包装一些类,

包装机制和值快照技术,完美解决了线程池环境下的上下文传递问题,是Java异步编程中不可或缺的工具。

// 包装普通Runnable
public static Runnable wrap(Runnable runnable) {Map<TransmittableThreadLocal<Object>, Object> captured = capture();return () -> {Map<TransmittableThreadLocal<Object>, Object> backup = replay(captured);try {runnable.run();} finally {restore(backup);}};
}

在capture/replay这俩个方法中有一定开销,因为是for循环遍历来操作数据的。我们需要避免存储大对象。

并且TTL是存在线程安全问题的,因为默认都是引用类型拷贝,如果子线程修改了数据,主线程是可以感知到的。

http://www.xdnf.cn/news/678565.html

相关文章:

  • Unity3D仿星露谷物语开发53之库存管理页面
  • Introduction to SQL
  • 【键盘说明书备份】ENERGYFORT
  • 编程日志5.27
  • MySQL :MySQL基本概念
  • 高性能计算 | 硅光芯片代工厂揭秘——技术特点与未来演进
  • SpringBoot集成jwt,实现token验证
  • 鸿蒙OSUniApp 实现自定义的侧边栏菜单组件#三方框架 #Uniapp
  • SQLord: 基于反向数据生成和任务拆解的 Text-to-SQL 企业落地方案
  • CMake 在尝试下载 Boost 时失败:SHA256 校验和与预期值不匹配
  • 【第1章 基础知识】1.8 在 Canvas 中使用 HTML 元素
  • 力扣HOT100之回溯:131. 分割回文串
  • 基于Matlab实现各种光谱数据预处理
  • Turf.js:前端地理空间分析的瑞士军刀
  • 2025山东CCPC补题
  • 基于Python的简易聊天机器人实现:从原理到实践
  • 组合API-provide和inject函数
  • 多模态机器学习
  • Android 开发:从 View Activity 向 Compose Activity 传递数据的多种实现方式
  • [yolov11改进系列]基于yolov11引入可改变核卷积AKConv的python源码+训练源码
  • QCustomPlot设置曲线图中文字缩放大小
  • 微信小程序一次性订阅封装
  • Linux 权限管理基础:深入理解 root 与 sudo 的用法
  • 【监控】Spring Boot 应用监控
  • libvirt设置虚拟机mtu实现原理
  • 决策树 GBDT XGBoost LightGBM
  • ETL数据集成过程全流程优化指南
  • ICMP与TCP端口:网络层与传输层解析
  • 尚硅谷redis7 49-51 redis管道之理论简介
  • Python的虚拟环境