• 阿里开源支持缓存线程池的ThreadLocal Transmittable ThreadLocal(TTL)


    功能

    在使用线程池等会缓存线程的组件情况下,提供ThreadLocal值的传递功能。

    JDK的InheritableThreadLocal类可以完成父子线程值的传递。 但对于使用线程池等会缓存线程的组件的情况,线程由线程池创建好,并且线程是缓存起来反复使用的;这时父子线程关系的上下文传递已经没有意义,应用中要做上下文传递,实际上是在把 任务提交给线程池时的上下文传递到 任务执行时。

    本库提供的TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题,使用详见User Guide

    欢迎

    需求场景

    在ThreadLocal的需求场景即是TTL的潜在需求场景,如果你的业务需要『在使用线程池等会缓存线程的组件情况下传递ThreadLocal』则是TTL目标场景。

    下面是几个典型场景例子。

    1. 分布式跟踪系统

    2. 应用容器或上层框架跨应用代码给下层SDK传递信息

    3. 日志收集记录系统上下文

    各个场景的展开说明参见子文档 需求场景

    User Guide

    使用类TransmittableThreadLocal来保存上下文,并跨线程池传递。

    TransmittableThreadLocal继承InheritableThreadLocal,使用方式也类似。

    InheritableThreadLocal,添加了protected方法copy,用于定制 任务提交给线程池时的上下文传递到 任务执行时时的拷贝行为,缺省是传递的是引用。

    具体使用方式见下面的说明。

    1. 简单使用

    父线程给子线程传递值。

    示例代码:

    // 在父线程中设置 
    TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
    parent.set("value-set-in-parent"); // ===================================================== 
    // 在子线程中可以读取, 值是"value-set-in-parent" 
    String value = parent.get();

    这是其实是InheritableThreadLocal的功能,应该使用InheritableThreadLocal来完成。

    但对于使用了异步执行(往往使用线程池完成)的情况,线程由线程池创建好,并且线程是缓存起来反复使用的。

    这时父子线程关系的上下文传递已经没有意义,应用中要做上下文传递,实际上是在把 任务提交给线程池时的上下文传递到任务执行时。 解决方法参见下面的这几种用法。

    2. 保证线程池中传递值

    2.1 修饰Runnable和Callable

    使用com.alibaba.ttl.TtlRunnablecom.alibaba.ttl.TtlCallable来修饰传入线程池的Runnable和Callable。

    示例代码:

    TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
    parent.set("value-set-in-parent");
    Runnable task = new Task("1"); // 额外的处理,生成修饰了的对象
    ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task); 
    executorService.submit(ttlRunnable); // ===================================================== 
    // Task中可以读取, 值是"value-set-in-parent"
    String value = parent.get();

    上面演示了Runnable,Callable的处理类似

    TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
    parent.set("value-set-in-parent"); Callable call = new Call("1"); // 额外的处理,生成修饰了的对象
    ttlCallable Callable ttlCallable = TtlCallable.get(call);
    executorService.submit(ttlCallable); // ===================================================== 
    // Call中可以读取, 值是"value-set-in-parent" String value = parent.get();

    整个过程的完整时序图

    时序图

    2.2 修饰线程池

    省去每次Runnable和Callable传入线程池时的修饰,这个逻辑可以在线程池中完成。

    通过工具类com.alibaba.ttl.threadpool.TtlExecutors完成,有下面的方法:

    • getTtlExecutor:修饰接口Executor

    • getTtlExecutorService:修饰接口ExecutorService

    • ScheduledExecutorService:修饰接口ScheduledExecutorService

    示例代码:

    ExecutorService executorService = ... // 额外的处理,生成修饰了的对象
    executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
    TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
    parent.set("value-set-in-parent");
    Runnable task = new Task("1"); Callable call = new Call("2");
    executorService.submit(task);
    executorService.submit(call); // ===================================================== 
    // Task或是Call中可以读取, 值是"value-set-in-parent"
    String value = parent.get();

    2.3 使用Java Agent来修饰JDK线程池实现类

    这种方式,实现线程池的传递是透明的,代码中没有修饰Runnable或是线程池的代码。
    # 即可以做到应用代码 无侵入,后面文档有结合实际场景的架构对这一点的说明。

    示例代码:

    // 框架代码 
    TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
    parent.set("value-set-in-parent"); // 应用代码 
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    Runnable task = new Task("1");
    Callable call = new Call("2");
    executorService.submit(task);
    executorService.submit(call); // ===================================================== 
    // Task或是Call中可以读取, 值是"value-set-in-parent" 
    String value = parent.get();

    Demo参见AgentDemo.java

    目前Agent中,修饰了jdk中的两个线程池实现类(实现代码在TtlTransformer.java):

    • java.util.concurrent.ThreadPoolExecutor

    • java.util.concurrent.ScheduledThreadPoolExecutor

    在Java的启动参数加上:

    • -Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.x.jar

    • -javaagent:/path/to/transmittable-thread-local-2.x.x.jar

    注意:

    • Agent修改是JDK的类,类中加入了引用TTL的代码,所以TTL Agent的Jar要加到bootclasspath上。

    Java命令行示例如下:

    java -Xbootclasspath/a:transmittable-thread-local-2.0.0.jar 
        -javaagent:transmittable-thread-local-2.0.0.jar 
        -cp classes 
        com.alibaba.ttl.threadpool.agent.demo.AgentDemo

    有Demo演示『使用Java Agent来修饰线程池实现类』,执行工程下的脚本run-agent-demo.sh即可运行Demo。

    Java Agent的使用方式在什么情况下TTL会失效

    由于Runnable和Callable的修饰代码,是在线程池类中插入的。下面的情况会让插入的代码被绕过,传递会失效。

    • 用户代码中继承java.util.concurrent.ThreadPoolExecutor和java.util.concurrent.ScheduledThreadPoolExecutor, 覆盖了execute、submit、schedule等提交任务的方法,并且没有调用父类的方法。
      修改线程池类的实现,execute、submit、schedule等提交任务的方法禁止这些被覆盖,可以规避这个问题。

    • 目前,没有修饰java.util.Timer类,使用Timer时,TTL会有问题。

    Java API Docs

    当前版本的Java API文档地址: http://alibaba.github.io/transmittable-thread-local/apidocs/

    Maven依赖

    示例:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>transmittable-thread-local</artifactId>
        <version>2.0.0</version>
    </dependency>

    可以在 search.maven.org 查看可用的版本。

    FAQ

    • Mac OS X下,使用javaagent,可能会报JavaLaunchHelper的出错信息。
      JDK Bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021205
      可以换一个版本的JDK。我的开发机上1.7.0_40有这个问题,1.6.0_51、1.7.0_45可以运行。
      # 1.7.0_45还是有JavaLaunchHelper的出错信息,但不影响运行。

    更多文档

  • 相关阅读:
    关于电商开发中金额的数据存储
    java编程IO简单回顾和学习
    不同数据源之间的数据同步jdbc解决方案
    简单的dialog,类似alert弹框
    PageInfo 前台分页js,带分页栏
    oracle 操作,偶尔记一下
    时间控件My97简单用法
    时间控件格式化,有了他,我再也不怕格式化时间了
    oracle 数据库io 异常,错误代码17002 解决办法
    列表渲染
  • 原文地址:https://www.cnblogs.com/xiaopotian/p/11056715.html
Copyright © 2020-2023  润新知