transmittable-thread-local 开发者指南:CRR 传递模型、Java Agent 无侵入增强与本地构建实战
后端并发编程【免费下载链接】transmittable-thread-local a missing Java std lib(simple 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.项目地址https://gitcode.com/gh_mirrors/tr/transmittable-thread-local点击查看免费下载本篇面向框架/中间件开发者围绕 TTL 仓库的开发者指南展开讲解如何用TransmittableThreadLocal.Transmitter的 CRRcapture-replay-restore操作在任意线程间传递 TTL 值、如何用 Java Agent 方式对 JDK 线程池做无侵入修饰、如何解决 Boot classpath 与 Javassist 依赖冲突问题以及如何在本地完成编译构建。读完后你可以将 TTL 集成进自己的框架或中间件并把 TTL Agent 嵌入已有的 Java Agent 体系。一、框架/中间件集成 TTL 传递CRR 操作模型对于不能修改业务代码中Runnable/Callable的框架与中间件场景TTL 提供了底层工具类TransmittableThreadLocal.Transmitter实现位于 Transmitter.java用于抓取当前线程的所有 TTL 值并在其他线程进行回放回放线程执行完业务操作后恢复为回放线程原来的 TTL 值。Transmitter提供了所有 TTL 值的抓取、回放和恢复方法即CRR 操作capture()方法抓取线程 A 的所有 TTL 值replay(captured)方法在线程 B 中回放capture抓取的 TTL 值并返回回放前 TTL 值的备份restore(backup)方法恢复线程 B 执行replay之前的 TTL 值即备份。示例代码// // 线程 A // TransmittableThreadLocalString context new TransmittableThreadLocal(); context.set(value-set-in-parent); // (1) 抓取当前线程的所有TTL值 final Object captured TransmittableThreadLocal.Transmitter.capture(); // // 线程 B异步线程 // // (2) 在线程 B中回放在capture方法中抓取的TTL值并返回 回放前TTL值的备份 final Object backup TransmittableThreadLocal.Transmitter.replay(captured); try { // 你的业务逻辑这里你可以获取到外面设置的TTL值 String value context.get(); System.out.println(Hello: value); ... String result World: value; } finally { // (3) 恢复线程 B执行replay方法之前的TTL值即备份 TransmittableThreadLocal.Transmitter.restore(backup); }1.1 用工具方法 Lambda 简化 replay/restoreTransmitter还提供了runSupplierWithCaptured和runCallableWithCaptured等工具方法配合 Java 8 Lambda 语法可以简化replay与restore操作// // 线程 A // TransmittableThreadLocalString context new TransmittableThreadLocal(); context.set(value-set-in-parent); // (1) 抓取当前线程的所有TTL值 final Object captured TransmittableThreadLocal.Transmitter.capture(); // // 线程 B异步线程 // String result runSupplierWithCaptured(captured, () - { // 你的业务逻辑这里你可以获取到外面设置的TTL值 String value context.get(); System.out.println(Hello: value); ... return World: value; }); // (2) (3)从 Transmitter.java 的源码结构看工具方法内部正是标准的 CRR 三步封装例如runSupplierWithCaptured的实现为先replay(captured)拿到backup在try块中执行业务逻辑finally中restore(backup)。提供runSupplierWithCaptured无受检异常与runCallableWithCapturedthrows Exception两个方法是为了满足不同throws类型的 lambda 函数接口源码 Javadoc 还指出如果业务需要其他异常签名可以自己仿照定义对应的工具方法。此外Transmitter还提供语义等价的clear()等价于replay(EMPTY_CAPTURE)以及配套的runSupplierWithClear、runCallableWithClear工具方法。需要特别注意的是Transmitter的 JavaDoc 明确将其定位为面向框架/中间件集成的内部操作 API一般业务代码不应直接调用它——业务侧应优先使用TtlRunnable/TtlCallable或TtlExecutors等公开封装。1.2 CRR 在 TtlRunnable / TtlCallable 中的落地实现更多 TTL 传递的代码实现可参见 TtlRunnable.java 与 TtlCallable.java。以TtlRunnable为例其内部调用链完整体现了 CRR 模型capture 时机构造函数中执行this.capturedRef new AtomicReference(capture())即在**提交任务的线程线程 A**创建包装对象时立即抓取 TTL 值而不是在run()执行时抓取replay/restore 时机run()中先replay(captured)得到backup在try块执行原始runnable.run()finally中restore(backup)确保线程池线程在执行完任务后恢复原值避免污染线程池中被复用的线程防重复执行run()开头通过capturedRef的空值与compareAndSet检查在releaseTtlValueReferenceAfterRun模式下任务只能执行一次重复执行会抛出IllegalStateExceptionget(runnable, releaseTtlValueReferenceAfterRun)工厂方法支持在运行后释放 TTL 值引用即使TtlRunnable对象仍被持有也不会有内存泄漏。TtlCallable的call()方法与TtlRunnable.run()结构完全对称构造函数 capture、call()中 replay → 执行业务 → restore并同样支持releaseTtlValueReferenceAfterCall。二、Java Agent对应用代码无侵入的线程池增强README 中「使用 Java Agent 来修饰 JDK 线程池实现类」一节指出相对修饰Runnable或修饰线程池的方式Java Agent 方式对应用代码是无侵入的。下面结合仓库实现做展开说明。按场景架构图把示例代码的操作可以分成三部分读取信息设置到 TTL这部分在容器如 Web 容器/入口线程中完成无需应用参与提交 Runnable 到线程池需要修饰操作Runnable无论是直接修饰Runnable还是修饰线程池这部分操作一定在用户应用中触发读取 TTL 做业务检查在 SDK 中完成无需应用参与。只有第 2 部分与用户应用代码相关。如果不通过 Java Agent 修饰线程池则这部分的修饰操作需要应用代码自己完成如显式调用TtlRunnable.get(...)或TtlExecutors.getTtlExecutorService(...)而使用 Java Agent 方式应用无需修改代码即可相对透明地完成跨线程池的上下文传递。更多关于应用场景的说明参见 需求场景。2.1 TTL Agent 的内部结构Transformlet 列表从源码结构看TTL Agent 的入口是 TtlAgent.java 的premain(String agentArgs, Instrumentation inst)方法。它在启动时构建TtlTransformlet列表并注册ClassFileTransformer// ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlAgent.premain 核心逻辑 transformletList.add(new JdkExecutorTtlTransformlet()); transformletList.add(new PriorityBlockingQueueTtlTransformlet()); transformletList.add(new ForkJoinTtlTransformlet()); if (isEnableTimerTask()) transformletList.add(new TimerTaskTtlTransformlet()); final ClassFileTransformer transformer new TtlTransformer(transformletList, isLogClassTransform()); inst.addTransformer(transformer, true);即 TTL Agent 通过四个内置 transformlet 分别增强 JDK 的ThreadPoolExecutor/ScheduledThreadPoolExecutor、PriorityBlockingQueue、ForkJoinPool以及TimerTask可选对应实现位于 transformlet/internal 包下。TtlTransformer.java 实现了ClassFileTransformer接口每次类加载时按顺序遍历 transformlet 列表任一 transformlet 修改了类字节码classInfo.isModified()就返回改写后的字节码对于 Lambda 类无类文件名与com.alibaba.ttl、java.lang包下的类直接跳过避免无谓处理。此外TtlTransformer还集成了扩展 transformlet 机制TtlExtensionTransformletManager允许外部通过 SPI 扩展增强规则示例工程见 sample-ttl-agent-extension-transformlet。2.2 TTL Agent 的配置项TtlAgent的 JavaDoc 中定义了完整的配置模型配置来源可以是-DJVM 参数格式-Dkey1v1 -Dkey2v2或 TTL Agent 参数格式key1:v1,key2:v2,分隔键值对、:分隔键与值优先读取-D属性仅当未配置-D属性时才读取 Agent 参数。可用配置键如下配置键默认值说明ttl.agent.loggerSTDERR日志输出类型取STDERR仅错误输出到 stderr或STDOUT开发期需要更详细日志ttl.agent.disable.inheritable.for.thread.poolfalse是否为线程池启用「禁用继承」即将ThreadPoolExecutor的ThreadFactory改写为DisableInheritableThreadFactory、将ForkJoinPool的工厂改写为DisableInheritableForkJoinWorkerThreadFactory避免子线程自动继承父线程 TTL 值ttl.agent.enable.timer.tasktrue自 2.11.2 起是否启用TimerTask类修饰ttl.agent.log.class.transformfalse是否记录被 TTL Agent 接收并转换的类便于排查 Agent 行为配置示例两种写法等价# -D property 方式 -Dttl.agent.loggerSTDOUT -Dttl.agent.disable.inheritable.for.thread.pooltrue # TTL agent arguments 方式 -javaagent:/path/to/transmittable-thread-local-2.x.y.jarttl.agent.logger:STDOUT,ttl.agent.disable.inheritable.for.thread.pool:true三、在已有 Java Agent 中嵌入 TTL Agent如果应用已经引入了自己的 Java Agent可以把自己的 Agent 与 TTL Agent 合并从而减少 Java 启动命令行上的 Agent 配置数量。做法是在自己的 Agent 的premain中先调用TtlAgent.premain示例代码参见 YourXxxAgent.javaimport com.alibaba.ttl.threadpool.agent.TtlAgent; import com.alibaba.ttl.threadpool.agent.TtlTransformer; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.logging.Logger; public final class YourXxxAgent { private static final Logger logger Logger.getLogger(YourXxxAgent.class.getName()); public static void premain(String agentArgs, Instrumentation inst) { TtlAgent.premain(agentArgs, inst); // add TTL Transformer // add your Transformer ... } }关于 Java Agent 和ClassFileTransformer的实现细节可参考 TtlAgent.java 与 TtlTransformer.javattl2-compatible 模块为 2.x 兼容包路径com.alibaba.ttlttl-agent 模块为 3.x 包路径com.alibaba.ttl3两者结构对应。注意即使在 bootclasspath 上还是要加上 TTL Jar-Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.y.jar:/path/to/your/agent/jar/files从 TtlAgent 的 JavaDoc 看自 v2.6.0 起 TTL Agent Jar 会通过 manifest 文件META-INF/MANIFEST.MF的Boot-Class-Path属性自动把自己加入 boot classpath构建时由 maven-jar-plugin 写入见 ttl-agent/pom.xml 中Boot-Class-Path${project.build.finalName}.jar的 manifestEntries 配置。因此不应修改 Maven 仓库中下载的 TTL Jar 文件名一旦改名如ttl-foo-name-changed.jar就必须手动通过-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar将其加入 boot classpath。四、Bootstrap ClassPath 上添加通用库 Jar 的问题及解决方法TTL Agent 的使用方式要求把 TTL Jar 加到 Bootstrap ClassPath 上通过 Java 命令行参数-Xbootclasspath这样 TTL 的类与 JDK 标准库类如java.lang.String的 ClassLoader 一致都在 Bootstrap ClassPath 上。Bootstrap ClassPath 上的类会优先于应用 ClassPath 的 Jar 被加载并且其加载 ClassLoader 不能被修改。严格来说通过 Bootstrap ClassPath 上的类如标准库类在技术上可以改动 ClassLoader 委派行为但这样做一般只会带来各种麻烦的问题实践中应避免。由此引出一个依赖冲突问题TTL Agent 内部实现使用了 Javassist 做字节码改写因此 Bootstrap ClassPath 上也需要 Javassist。如果应用自身也依赖 Javassist由于运行时会优先使用 TTL Agent 配置在 Bootstrap ClassPath 上的 Javassist应用逻辑运行时实际无法选择/指定自己 ClassPath 中的 Javassist 版本从而带来应用所需 Javassist 与 TTL Agent 所用 Javassist 之间的兼容性风险。解决方法是repackage 依赖重命名/改写依赖类的包名用 Maven Shade 插件把 Javassist 的类文件重新打包并添加到 TTL Jar 中。仓库中 ttl-agent/pom.xml 的 maven-shade-plugin 配置正是这一方案的落地实现relocations relocation patternjavassist/pattern shadedPatterncom.alibaba.ttl3.agent.transformlet.javassist/shadedPattern /relocation /relocations artifactSet includes includeorg.javassist:javassist/include includecom.alibaba.ttl3:ttl-core/include /includes /artifactSet即把javassist包重定位relocation为com.alibaba.ttl3.agent.transformlet.javassist并将org.javassist:javassist与ttl-core打入同一个 Agent Jar。这样操作后TTL Agent 不再依赖外部的 Javassist 依赖效果上这个 shade 过的 TTL Jar 是自包含的、在使用上编译/运行时无外部依赖自然规避了与应用中 Javassist 的依赖冲突问题。五、如何编译构建编译构建的环境要求JDK 8用 Maven 常规方式执行编译构建即可。工程中已包含符合版本要求的 Maven Wrapper直接运行工程根目录下的mvnw即可不需要先手动安装 Maven# 运行测试Case ./mvnw test # 编译打包 ./mvnw package # 运行测试Case、编译打包、安装TTL库到Maven本地 ./mvnw install ##################################################### # 如果使用你自己安装的 maven版本要求maven 3.3.9 mvn install发布相关的完整操作版本升级、发布到仓库等步骤详见独立文档 发布操作列表。六、相关资料开发 TTL Agent 相关的扩展时以下 JDK 与工具链主题是重要的背景知识对应仓库文档中的原始资料清单JDK core classesWeakHashMapTTL 清理机制的底层容器之一、InheritableThreadLocalTTL 继承语义的基类Java Agent 机制Java Agent 规范java.lang.instrument包的 Instrumentation 机制、JAR Manifest 规范、Manifest 文件操作指南以及社区关于 Instrumentation、Agent 动态加载与加载机制分析的文章JavassistJavassist 入门教程TTL Agent 字节码改写的底层库Maven Shade 插件maven-shade-plugin 官方文档本文第四节的 repackage/relocation 操作即基于该插件的 class-relocation 能力。这些主题在 developer-guide-en.md 英文版中有对应条目可结合使用。赞分享后端并发编程【免费下载链接】transmittable-thread-local a missing Java std lib(simple 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.项目地址https://gitcode.com/gh_mirrors/tr/transmittable-thread-local点击查看免费下载相关推荐用 Ruby on Rails 构建 JavaScript 应用后端AJAX、数据传递与无侵入式 JavaScript 实战指南用 Ruby on Rails 构建 JavaScript 应用后端AJAX、数据传递与无侵入式 JavaScript 实战指南 导读 当你已经掌握了大量客户文档教程教育AspectJXAndroid开发者的AOP终极利器轻松实现无侵入式代码增强AspectJXAndroid开发者的AOP终极利器轻松实现无侵入式代码增强 AspectJX是一款专为Android开发者打造的Gradle插件它能将ATransmittableThreadLocal(TTL) 深度指南线程池场景下 ThreadLocal 值传递的原理、三种用法与 Java Agent 无侵入方案TransmittableThreadLocal TTL 深度指南线程池场景下 ThreadLocal 值传递的原理、三种用法与 Java Agent 无侵入后端并发编程上一篇PGLoader终极指南高效数据迁移的完整解决方案下一篇如何在非macOS系统中完美处理DMG文件DMG2IMG跨平台转换工具完整教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考