zoukankan      html  css  js  c++  java
  • 阿里开源支持缓存线程池的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的出错信息,但不影响运行。

    更多文档

  • 相关阅读:
    Linked List Cycle leetcode java (链表检测环)
    Remove Duplicates from Sorted List II leetcode java
    Remove Duplicates from Sorted List leetcode java
    Merge Two Sorted Lists leetcode java
    Swap Nodes in Pairs leetcode java
    Median of Two Sorted Array leetcode java
    阿里云最便宜的四种域名注册
    nohup和&后台运行,进程查看及终止
    ipv6转ipv4 NAT64与DNS64基本原理概述
    ros使用pppoe拨号获取ipv6,并且下发IPV6的dns到客户机win7
  • 原文地址:https://www.cnblogs.com/xiaopotian/p/11056715.html
Copyright © 2011-2022 走看看