a missing Java std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even
a missing Java std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even
关于具体使用方式见下面的说明。 ## 1. 简单使用 父线程给子线程传递值。 示例代码: ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); // ===================================================== // 在子线程中可以读取,值是"value-set-in-parent" String value = context.get(); ``` \# 完整可运行的Demo代码参见[`SimpleDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/SimpleDemo.kt)。 这其实是`InheritableThreadLocal`的功能,应该使用`InheritableThreadLocal`来完成。 但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的`ThreadLocal`值传递已经没有意义,应用需要的实际上是把 **任务提交给线程池时**的`ThreadLocal`值传递到 **任务执行时**。 解决方法参见下面的这几种用法。 ## 2. 保证线程池中传递值 ### 2.1 修饰`Runnable`和`Callable` 使用[`TtlRunnable`](ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java)和[`TtlCallable`](ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java)来修饰传入线程池的`Runnable`和`Callable`。 示例代码: ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); Runnable task = new RunnableTask(); // 额外的处理,生成修饰了的对象ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable); // ===================================================== // Task中可以读取,值是"value-set-in-parent" String value = context.get(); ``` **_注意_**: 即使是同一个`Runnable`任务多次提交到线程池时,每次提交时都需要通过修饰操作(即`TtlRunnable.get(task)`)以抓取这次提交时的`TransmittableThreadLocal`上下文的值;即如果同一个任务下一次提交时不执行修饰而仍然使用上一次的`TtlRunnable`,则提交的任务运行时会是之前修饰操作所抓取的上下文。示例代码如下: ```java // 第一次提交 Runnable task = new RunnableTask(); executorService.submit(TtlRunnable.get(task)); // ...业务逻辑代码, // 并且修改了 TransmittableThreadLocal上下文 ... context.set("value-modified-in-parent"); // 再次提交 // 重新执行修饰,以传递修改了的 TransmittableThreadLocal上下文 executorService.submit(TtlRunnable.get(task)); ``` 上面演示了`Runnable`,`Callable`的处理类似 ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); Callable call = new CallableTask(); // 额外的处理,生成修饰了的对象ttlCallable Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable); // ===================================================== // Call中可以读取,值是"value-set-in-parent" String value = context.get(); ``` \# 完整可运行的Demo代码参见[`TtlWrapperDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlWrapperDemo.kt)。 #### 整个过程的完整时序图 ### 2.2 修饰线程池 省去每次`Runnable`和`Callable`传入线程池时的修饰,这个逻辑可以在线程池中完成。 通过工具类[`TtlExecutors`](ttl-core/src/main/java/com/alibaba/ttl3/executor/TtlExecutors.java)完成,有下面的方法: - `getTtlExecutor`:修饰接口`Executor` - `getTtlExecutorService`:修饰接口`ExecutorService` - `getTtlScheduledExecutorService`:修饰接口`ScheduledExecutorService` 示例代码: ``` … ``` \# 完整可运行的Demo代码参见[`TtlExecutorWrapperDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlExecutorWrapperDemo.kt)。 ### 2.3 使用`Java Agent`来修饰`JDK`线程池实现类 这种方式,实现线程池的传递是透明的,业务代码中没有修饰`Runnable`或是线程池的代码。即可以做到应用代码 **无侵入**。 \# 关于 **无侵入** 的更多说明参见文档[`Java Agent`方式对应用代码无侵入](docs/developer-guide.md#java-agent%E6%96%B9%E5%BC%8F%E5%AF%B9%E5%BA%94%E7%94%A8%E4%BB%A3%E7%A0%81%E6%97%A0%E4%BE%B5%E5%85%A5)。 示例代码: ```java // ## 1. 框架上层逻辑,后续流程框架调用业务 ## TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); // ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ## ExecutorService executorService = Executors.newFixedThreadPool(3); Runnable task = new RunnableTask(); Callable call = new CallableTask(); executorService.submit(task); executorService.submit(call); // ## 3. 框架下层逻辑 ## // Task或是Call中可以读取,值是"value-set-in-parent" String value = context.get(); ``` Demo参见[`AgentDemo.kt`](ttl2-compatible/src/test/java/com/alibaba/demo/ttl/agent/AgentDemo.kt)。执行工程下的脚本[`scripts/run-agent-demo.sh`](scripts/run-agent-demo.sh)即可运行Demo。 目前`TTL Agent`中,修饰了的`JDK`执行器组件(即如线程池)如下: 1. `java.util.concurrent.ThreadPoolExecutor` 和 `java.util.concurrent.ScheduledThreadPoolExecutor` - 修饰实现代码在[`JdkExecutorTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/JdkExecutorTtlTransformlet.java)。 1. `java.util.concurrent.ForkJoinTask`(对应的执行器组件是`java.util.concurrent.ForkJoinPool`) - 修饰实现代码在[`ForkJoinTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/ForkJoinTtlTransformlet.java)。从版本 **_`2.5.1`_** 开始支持。 - **_注意_**:`Java 8`引入的[**_`CompletableFuture`_**](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/CompletableFuture.html)与(并行执行的)[**_`Stream`_**](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/package-summary.html)底层是通过`ForkJoinPool`来执行,所以支持`ForkJoinPool`后,`TTL`也就透明支持了`CompletableFuture`与`Stream`。 1. `java.util.TimerTask`的子类(对应的执行器组件是`java.util.Timer`) - 修饰实现代码在[`TimerTaskTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/TimerTaskTtlTransformlet.java)。从版本 **_`2.7.0`_** 开始支持。 - **_注意_**:从`2.11.2`版本开始缺省开启`TimerTask`的修饰(因为保证正确性是第一位,而不是最佳实践『不推荐使用`TimerTask`』:);`2.11.1`版本及其之前的版本没有缺省开启`TimerTask`的修饰。 - 使用`Agent`参数`ttl.agent.enable.timer.task`开启/关闭`TimerTask`的修饰: - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:true` - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false` - 更多关于`TTL Agent`参数的配置说明详见[`TtlAgent.java`的JavaDoc](ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlAgent.java)。transmitteeValue方法 的 展开说明
关于构词后缀
er与ee的说明:
transmit是动词传递,transmitter动作的执行者/主动方,而transmittee动作的接收者/被动方。er与ee后缀的常见词是employer(雇主)/employee(雇员)、caller(调用者)/callee(被调用者)。
关于#### `Java Agent`的启动参数配置 在`Java`的启动java.util.TimerTask/java.util.Timer的 展开说明
Timer是JDK 1.3的老类,不推荐使用Timer类。推荐用
ScheduledExecutorService。
ScheduledThreadPoolExecutor实现更强壮,并且功能更丰富。 如支持配置线程池的大小(Timer只有一个线程);Timer在Runnable中抛出异常会中止定时执行。更多说明参见 10. Mandatory Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. - Alibaba Java Coding Guidelines。
No open issues yet, or sync has not completed.