Skip to content

🐙 multi-thread context(MTC), a simple java lib for transmitting context between threads even using thread pool.

License

Notifications You must be signed in to change notification settings

liuylmr/multi-thread-context

 
 

Repository files navigation

multi-thread context(MTC)

Build Status Coverage Status Maven Central GitHub release
Dependency Status GitHub issues License

🔧 功能

👉 在使用线程池等会缓存线程的组件情况下,完成多线程的Context传递。

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

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

欢迎提建议或问题(提交Issue)和Fork后提交代码

🎨 需求场景

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

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

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

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

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

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

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

日志记录系统上下文

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

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

构架图

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

整体流程:

  1. 请求进入PAAS容器,提取上下文信息并设置好上下文。
  2. 进入用户应用处理业务,业务调用SDK(如DB、消息、etc)。
    用户应用会使用线程池,所以调用SDK的线程可能不是请求的线程。
  3. 进入SDK处理。
    提取上下文的信息,决定是否符合拒绝处理。

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

📓 User Guide

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

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

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

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

  1. 简单使用

父线程给子线程传递Context。

示例代码:

// 在父线程中设置
MtContextThreadLocal<String> parent = new MtContextThreadLocal<String>();
parent.set("value-set-in-parent");

// =====================================================

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

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

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

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

  1. 保证线程池中传递Context

2.1 修饰RunnableCallable

使用com.alibaba.mtc.MtContextRunnablecom.alibaba.mtc.MtContextCallable来修饰传入线程池的RunnableCallable

示例代码:

MtContextThreadLocal<String> parent = new MtContextThreadLocal<String>();
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(); 

上面演示了RunnableCallable的处理类似

MtContextThreadLocal<String> parent = new MtContextThreadLocal<String>();
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(); 

这种使用方式的时序图

时序图

2.2 修饰线程池

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

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

  • getMtcExecutor:修饰接口Executor
  • getMtcExecutorService:修饰接口ExecutorService
  • ScheduledExecutorService:修饰接口ScheduledExecutorService

示例代码:

ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象executorService
executorService = MtContextExecutors.getMtcExecutorService(executorService); 

MtContextThreadLocal<String> parent = new MtContextThreadLocal<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线程池实现类

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

示例代码:

// 框架代码
MtContextThreadLocal<String> parent = new MtContextThreadLocal<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中的两个线程池实现类(实现代码在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 AgentJar要加到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会失效?

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

  • 用户代码中继承java.util.concurrent.ThreadPoolExecutorjava.util.concurrent.ScheduledThreadPoolExecutor, 覆盖了executesubmitschedule等提交任务的方法,并且没有调用父类的方法。
    修改线程池类的实现,executesubmitschedule等提交任务的方法禁止这些被覆盖,可以规避这个问题。
  • 目前,没有修饰java.util.Timer类,使用Timer时,MtContext会有问题。

🔌 Java API Docs

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

🍪 Maven依赖

示例:

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>multithread.context</artifactId>
	<version>1.2.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_511.7.0_45可以运行。
    # 1.7.0_45还是有JavaLaunchHelper的出错信息,但不影响运行。

🗿 更多文档

📚 相关资料

Jdk core classes

Java Agent

About

🐙 multi-thread context(MTC), a simple java lib for transmitting context between threads even using thread pool.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 96.3%
  • Shell 3.4%
  • HTML 0.3%