当前位置:  软件>java软件

多线程传递Context multi-thread context(MTC)

    来源:    发布时间:2014-12-27

    本文导语:  multi-thread context(MTC)   English Documentation  功能  在使用线程池等会缓存线程的组件情况下,完成多线程的Context传递。 JDK的java.lang.InheritableThreadLocal类可以完成父子线程的Context传递。 但对于使用线程池等会缓存线程的组件的...

multi-thread context(MTC)

多线程传递Context multi-thread context(MTC)[图片] 多线程传递Context multi-thread context(MTC)[图片]

English Documentation

多线程传递Context multi-thread context(MTC)[图片] 功能

多线程传递Context multi-thread context(MTC)[图片] 在使用线程池等会缓存线程的组件情况下,完成多线程的Context传递。

JDK的java.lang.InheritableThreadLocal类可以完成父子线程的Context传递。

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

如有问题欢迎:

多线程传递Context multi-thread context(MTC)[图片] 需求场景 应用容器或上层框架跨应用代码给下层SDK传递信息

举个场景,App Engine(PAAS)上会运行由应用提供商提供的应用(SAAS模式)。多个SAAS用户购买并使用这个应用(即SAAS应用)。SAAS应用往往是一个实例为多个SAAS用户提供服务。
# 另一种模式是:SAAS用户使用完全独立一个SAAS应用,包含独立应用实例及其后的数据源(如DB、缓存,etc)。

需要避免的SAAS应用拿到多个SAAS用户的数据。

一个解决方法是处理过程关联一个SAAS用户的上下文,在上下文中应用只能处理(读&写)这个SAAS用户的数据。

请求由SAAS用户发起(如从Web请求进入App Engine),App Engine可以知道是从哪个SAAS用户,在Web请求时在上下文中设置好SAAS用户ID。

应用处理数据(DB、Web、消息 etc.)是通过App Engine提供的服务SDK来完成。当应用处理数据时,SDK检查数据所属的SAAS用户是否和上下文中的SAAS用户ID一致,如果不一致则拒绝数据的读写。

应用代码会使用线程池,并且这样的使用是正常的业务需求。SAAS用户ID的从要App Engine传递到下层SDK,要支持这样的用法。

日志记录系统上下文

App Engine的日志(如,SDK会记录日志)要记录系统上下文。由于不限制用户应用使用线程池,系统的上下文需要能跨线程的传递,且不影响应用代码。

上面场景使用MTC的整体构架

多线程传递Context multi-thread context(MTC)[图片]

构架涉及3个角色:容器、用户应用、SDK。

整体流程:

  • 请求进入PAAS容器,提取上下文信息并设置好上下文。

  • 进入用户应用处理业务,业务调用SDK(如DB、消息、etc)。
    用户应用会使用线程池,所以调用SDK的线程可能不是请求的线程。

  • 进入SDK处理。
    提取上下文的信息,决定是否符合拒绝处理。

  • 整个过程中,上下文的传递 对于 用户应用代码 期望是透明的。

    多线程传递Context multi-thread context(MTC)[图片] User Guide

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

    MtContextThreadLocal继承java.lang.InheritableThreadLocal,使用方式也类似。

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

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

    1. 简单使用

    父线程给子线程传递Context。

    示例代码:

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

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

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

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

    2. 保证线程池中传递Context 2.1 修饰Runnable和Callable

    使用com.alibaba.mtc.MtContextRunnablecom.alibaba.mtc.MtContextCallable来修饰传入线程池的Runnable和Callable。

    示例代码:

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

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

    MtContextThreadLocal parent = new MtContextThreadLocal(); parent.set("value-set-in-parent"); Callable call = new Call("1"); // 额外的处理,生成修饰了的对象mtContextCallable Callable mtContextCallable = MtContextCallable.get(call); executorService.submit(mtContextCallable); // ===================================================== // Call中可以读取, 值是"value-set-in-parent" String value = parent.get();
    这种使用方式的时序图

    多线程传递Context multi-thread context(MTC)[图片]

    2.2 修饰线程池

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

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

    • getMtcExecutor:修饰接口Executor

    • getMtcExecutorService:修饰接口ExecutorService

    • ScheduledExecutorService:修饰接口ScheduledExecutorService

    示例代码:

    ExecutorService executorService = ... // 额外的处理,生成修饰了的对象executorService executorService = MtContextExecutors.getMtcExecutorService(executorService); MtContextThreadLocal parent = new MtContextThreadLocal(); 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线程池实现类

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

    示例代码:

    // 框架代码 MtContextThreadLocal parent = new MtContextThreadLocal(); 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中的两个线程池实现类(实现代码在MtContextTransformer.java):

    • java.util.concurrent.ThreadPoolExecutor

    • java.util.concurrent.ScheduledThreadPoolExecutor

    在Java的启动参数加上:

    • -Xbootclasspath/a:/path/to/multithread.context-1.1.0.jar

    • -javaagent:/path/to/multithread.context-1.1.0.jar

    注意:

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

    Java命令行示例如下:

    java -Xbootclasspath/a:multithread.context-1.1.0.jar  -javaagent:multithread.context-1.1.0-SNAPSHOT.jar  -cp classes  com.alibaba.mtc.threadpool.agent.demo.AgentDemo

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

    什么情况下,Java Agent的使用方式MtContext会失效?

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

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

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

    多线程传递Context multi-thread context(MTC)[图片] Developer Guide Java Agent方式对应用代码无侵入

    相对修饰Runnble或是线程池的方式,Java Agent方式为什么是应用代码无侵入的?

    多线程传递Context multi-thread context(MTC)[图片]

    按框架图,把前面示例代码操作可以分成下面几部分:

  • 读取信息设置到MtContext。
    这部分在容器中完成,无需应用参与。

  • 提交Runnable到线程池。要有修饰操作Runnable(无论是直接修饰Runnble还是修饰线程池)。
    这部分操作一定是在用户应用中触发。

  • 读取MtContext,做业务检查。
    在SDK中完成,无需应用参与。

  • 只有第2部分的操作和应用代码相关。

    如果不通过Java Agent修饰线程池,则修饰操作需要应用代码来完成。

    使用Java Agent方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。

    如何权衡Java Agent方式的失效情况

    把这些失效情况都解决了是最好的,但复杂化了实现。下面是一些权衡:

    • 不推荐使用Timer类,推荐用ScheduledThreadPoolExecutor。 ScheduledThreadPoolExecutor实现更强壮,并且功能更丰富。 如支持配置线程池的大小(Timer只有一个线程);Timer在Runnable中抛出异常会中止定时执行。

    • 覆盖了execute、submit、schedule的问题的权衡是: 业务上没有修改这些方法的需求。并且线程池类提供了beforeExecute方法用于插入扩展的逻辑。

    已有Java Agent中嵌入MtContext Agent

    这样可以减少Java命令上Agent的配置。

    在自己的ClassFileTransformer中调用MtContextTransformer,示例代码如下:

    public class TransformerAdaptor implements ClassFileTransformer { final MtContextTransformer mtContextTransformer = new MtContextTransformer(); @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { final byte[] transform = mtContextTransformer.transform( loader, className, classBeingRedefined, protectionDomain, classfileBuffer); if (transform != null) { return transform; } // Your transform code ... return null; } }

    注意还是要在bootclasspath上,加上MtContext依赖的2个Jar:

    -Xbootclasspath/a:/path/to/multithread.context-1.1.0.jar:/path/to/your/agent/jar/files
    Bootstrap上添加通用库的Jar的问题及解决方法

    通过Java命令参数-Xbootclasspath把库的Jar加Bootstrap ClassPath上。Bootstrap ClassPath上的Jar中类会优先于应用ClassPath的Jar被加载,并且不能被覆盖。

    MTC在Bootstrap ClassPath上添加了Javassist的依赖,如果应用中如果使用了Javassist,实际上会优先使用Bootstrap ClassPath上的Javassist,即应用不能选择Javassist的版本,应用需要的Javassist和MTC的Javassist有兼容性的风险。

    可以通过repackage(重新命名包名)来解决这个问题。

    Maven提供了Shade插件,可以完成repackage操作,并把Javassist的类加到MTC的Jar中。

    这样就不需要依赖外部的Javassist依赖,也规避了依赖冲突的问题。

    多线程传递Context multi-thread context(MTC)[图片] Java API Docs

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

    多线程传递Context multi-thread context(MTC)[图片] Maven依赖

    示例:

     com.alibaba multithread.context 1.1.0 

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

    多线程传递Context multi-thread context(MTC)[图片] 性能测试 内存泄漏

    对比测试MtContextThreadLocalThreadLocal,测试Case是:

    简单一个线程一直循环new MtContextThreadLocal、ThreadLocal实例,不主动做任何清理操作,即不调用ThreadLocal的remove方法主动清空。

    验证结果

    都可以持续运行,不会出内存溢出OutOfMemoryError。

    执行方式

    可以通过执行工程下的脚本来运行Case验证:

    TPS & 压力测试

    对比测试MtContextThreadLocalThreadLocal,测试Case是:

    2个线程并发一直循环new MtContextThreadLocal、ThreadLocal实例,不主动做任何清理操作,即不调用ThreadLocal的remove方法主动清空。

    验证结果

    在我的4核开发机上运行了24小时,稳定正常。

    TPS结果如下:

    ThreadLocal的TPS稳定在~41K:

    ......
    tps: 42470
    tps: 40940
    tps: 41041
    tps: 40408
    tps: 40610

    MtContextThreadLocal的TPS稳定在~40K:

    ......
    tps: 40461 
    tps: 40101 
    tps: 39989 
    tps: 40684 
    tps: 41174

    GC情况如下(1分钟输出一次):

    ThreadLocal的每分钟GC时间是5.45s,FGC次数是0.09:

       S0     S1      E      O      P    YGC      YGCT     FGC     FGCT   GCT
    ......
      0.00  97.66   0.00   8.33  12.70 1470935 2636.215    41    0.229 2636.444
     97.66   0.00   0.00  17.18  12.70 1473968 2640.597    41    0.229 2640.825
     98.44   0.00   0.00  25.47  12.70 1477020 2645.265    41    0.229 2645.493
     96.88   0.00  33.04  34.03  12.70 1480068 2650.149    41    0.229 2650.378
      0.00  97.66  14.01  41.82  12.70 1483113 2655.262    41    0.229 2655.490
      0.00  97.66  74.07  50.25  12.70 1486149 2660.596    41    0.229 2660.825
     96.88   0.00   0.00  58.32  12.70 1489170 2666.135    41    0.229 2666.364
     98.44   0.00  26.07  67.05  12.70 1492162 2671.841    41    0.229 2672.070
      0.00  97.66   0.00  76.50  12.70 1495139 2677.809    41    0.229 2678.038
      0.00  97.66   0.00  85.95  12.70 1498091 2683.994    41    0.229 2684.222
     96.88   0.00   0.00  96.50  12.70 1501038 2690.454    41    0.229 2690.683
     97.66   0.00   0.00   7.96  12.70 1504054 2695.583    42    0.233 2695.816
      0.00  97.66   0.00  17.46  12.70 1507099 2700.009    42    0.233 2700.241
      0.00  97.66   0.00  26.97  12.70 1510133 2704.652    42    0.233 2704.885
     97.66   0.00   0.00  36.57  12.70 1513158 2709.592    42    0.233 2709.825
      0.00  97.66   0.00  45.59  12.70 1516167 2714.738    42    0.233 2714.971
     98.44   0.00   0.00  54.49  12.70 1519166 2720.109    42    0.233 2720.342
      0.00  98.44   0.00  63.52  12.70 1522141 2725.688    42    0.233 2725.921
      0.00  97.66  84.18  72.00  12.70 1525139 2731.579    42    0.233 2731.812
      0.00  98.44  20.04  80.10  12.70 1528121 2737.680    42    0.233 2737.913
      0.00  97.66  28.06  87.70  12.70 1531093 2743.991    42    0.233 2744.224
      0.00  98.44   0.00  95.63  12.70 1534055 2750.508    42    0.233 2750.741
     97.66   0.00   0.00   4.75  12.70 1537062 2756.196    43    0.239 2756.435

    MtContextThreadLocal的每分钟GC时间是5.29s,FGC次数是3.27:

       S0     S1      E      O      P    YGC      YGCT     FGC     FGCT   GCT
    ......
      0.00  98.44   8.01  57.38  12.80 1390879 2571.496  1572    9.820 2581.315
      0.00  97.66   0.00  78.87  12.80 1393725 2576.784  1575    9.839 2586.623
     98.44   0.00  14.04   5.83  12.80 1396559 2582.082  1579    9.866 2591.948
     98.44   0.00   0.00  26.41  12.80 1399394 2587.274  1582    9.885 2597.159
     98.44  98.44   0.00  50.75  12.80 1402230 2592.506  1585    9.904 2602.410
     98.44   0.00   0.00  84.37  12.80 1405077 2597.808  1588    9.925 2607.733
      0.00  98.44   0.00   5.19  12.80 1407926 2603.108  1592    9.952 2613.059
      0.00  98.44  58.17  29.80  12.80 1410770 2608.314  1595    9.973 2618.287
     99.22   0.00   0.00  54.14  12.80 1413606 2613.582  1598    9.992 2623.574
     98.44   0.00   0.00  78.18  12.80 1416444 2618.881  1601   10.012 2628.893
      0.00  97.66   0.00   7.36  12.80 1419275 2624.167  1605   10.038 2634.205
      0.00  99.22   0.00  31.04  12.80 1422125 2629.391  1608   10.057 2639.448
      0.00  98.44   0.00  60.41  12.80 1424974 2634.636  1611   10.077 2644.714
      0.00  98.44   0.00  84.72  12.80 1427825 2639.929  1614   10.094 2650.024
      0.00  97.66   0.00  12.32  12.80 1430679 2645.204  1618   10.119 2655.323
      0.00  98.44  12.05  39.31  12.80 1433539 2650.442  1621   10.141 2660.583
     86.81   0.00   0.00  67.40  12.80 1436392 2655.743  1624   10.156 2665.899
     99.22   0.00   0.00  95.25  12.80 1439244 2661.071  1627   10.175 2671.246
     98.44   0.00   0.00  24.63  12.80 1442090 2666.305  1631   10.201 2676.506
      0.00  99.22   0.00  52.86  12.80 1444945 2671.546  1634   10.222 2681.769
     98.44   0.00   0.00  80.38  12.80 1447802 2676.850  1637   10.241 2687.091
      0.00  87.50   0.00   4.22  12.80 1450658 2682.196  1641   10.268 2692.464
     99.22   0.00   0.00  33.22  12.80 1453507 2687.386  1644   10.290 2697.676
    TPS略有下降的原因分析

    使用jvisualvm Profile方法耗时,MtContextThreadLocalCase的热点方法和ThreadLocalCase一样。

    略有下降可以认为是Full GC更多引起。

    实际使用场景中,MtContextThreadLocal实例个数非常有限,不会有性能问题。

    FGC次数增多的原因分析

    在MtContextThreadLocal.holder中,持有MtContextThreadLocal实例的弱引用,减慢实例的回收,导致Full GC增加。

    实际使用场景中,MtContextThreadLocal实例个数非常有限,不会有性能问题。

    执行方式

    可以通过执行工程下的脚本来运行Case验证:

    多线程传递Context multi-thread context(MTC)[图片] 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的出错信息,但不影响运行。

    多线程传递Context multi-thread context(MTC)[图片] 相关资料 Jdk core classes Java Agent Javassist Shade插件

        
     
     
     
    本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
    本站(WWW.)站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。












  • 相关文章推荐
  • Java中多线程相关类Thread介绍
  • 一个进程创建了两个线程,如何使得当任何一个线程(比如线程a)结束时,同时也结束线程b,也就是使两个线程一起死掉,怎么办呢?
  • c#多线程更新窗口(winform)GUI的数据
  • java 线程,对当前线程(非主线程)调用sleep,为什么主线程(窗口)也没反应了
  • Windows和Linux下C++类成员方法作为线程函数方法介绍
  • 如何实现一个线程组内多线程的非同不执行,即一个线程执行完毕后再执行下一个线程???
  • c++的boost库多线程(Thread)编程(线程操作,互斥体mutex,条件变量)详解
  • 请问:进程创建的线程是怎样运行的啊,线程的处理函数运行完了,线程就退出了吗?
  • Linux下GCC内置原子操作函数(多线程资源访问)介绍
  • 关于线程的问题,什么样的线程不是active线程?
  • 请问Linux核心支持多线程吗?开发库有线程库吗?线程好用吗?(稳定?)
  • 请问,在一个进程中创建多线程时如何能避免不同的线程获得同一个线程标识
  • 我的一个多线程服务里, 总是有一个线程莫名其妙的变成僵尸线程。
  • 能否通过线程id控制线程的状态?或是观察到线程的状态?
  • 如何在一个线程中启动另外一个线程,然后本线程就退出?
  • 我要设置一个线程的优先级, 这个属性结构并没有线程的id,它怎么知道是设置哪个线程呢?
  • 请问在java多线程中,是只有run(){}内的代码运行在一个新线程下呢?还是这个类中的代码都运行在一个新线程下?
  • gcc链接的库,分不分单线程版本的和多线程版本的?
  • 内核栈~ 内核线程 ~用户线程 之间关系 问题
  • 子线程的数据如何返回给主线程?
  • 如果父线程死掉 那么子线程会不会死掉呢


  • 站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3