问题描述
在日常的开发中,我们经常会通过多线程来提高业务执行效率,例如:
当前登录用户信息放在ThreadLocal内,然后service在处理业务逻辑时通过线程池来异步的处理,由于线程池内的线程与当前主线程不是同一个,因此获取不到主线程存放的用户信息
Runnable runnable = ()->{
while(true){
//处理用户数据-会从ThreadLoca内获取登录人信息
System.out.println("当前ThreadLocal值为=>"+threadLocal.get());
try {
ThreadUtil.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
Runnable runnable2 = ()->{
//处理订单数据-会从ThreadLoca内获取登录人信息
System.out.println("当前ThreadLocal值为=>"+threadLocal.get());
};
//异步处理用户数据
CompletableFuture.runAsync(runnable,executor);
//主线程操作
threadLocal.set("hello TTL");
//异步处理订单数据
CompletableFuture.runAsync(runnable2,executor);
JDK为我们提供了 InheritableThreadLocal
,但是他只有在创建新线程时才会拷贝(一个新线程只拷贝一次),而线程池内的核心线程是不会销毁的,会处理多个任务,因此就无法获取到当前登录人信息(或者会获取其他人的登录信息)。
这种情况我们就可以使用Alibaba
为我们提供的 TransmittableThreadLocal
来解决这个问题。
TTL使用
TransmittableThreadLocal继承自InheritableThreadLocal,并扩展了多次拷贝主线程ThreadLocal的功能。
示例:
/**
*打印结果:
修改为hello TTL
pool-1-thread-1当前ThreadLocal值为=>0
pool-1-thread-1当前ThreadLocal值为=>0
pool-1-thread-1当前ThreadLocal值为=>0
pool-1-thread-1当前ThreadLocal值为=>hello TTL
pool-1-thread-1当前ThreadLocal值为=>hello TTL
pool-1-thread-1当前ThreadLocal值为=>hello TTL
*/
public class TestTTL {
//创建线程池
static ExecutorService executor = new ThreadPoolExecutor(
1,
1,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.CallerRunsPolicy()
);
//使用TransmittableThreadLocal
static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static void main(String[] args){
threadLocal.set("0");
Runnable runnable = ()->{
for(int i=0;i<3;i++){
//处理用户数据
System.out.println(Thread.currentThread().getName()+"当前ThreadLocal值为=>"+threadLocal.get());
ThreadUtil.sleep(1000);
}
};
//执行任务1 注意此处需要通过 TtlRunnable.get(runnable)改变runnable类
CompletableFuture.runAsync(TtlRunnable.get(runnable), executor);
//主线程 修改 ThreadLocal
threadLocal.set("hello TTL");
System.out.println("修改为hello TTL");
//执行任务2
CompletableFuture.runAsync(TtlRunnable.get(runnable),executor);
executor.shutdown();
}
}
Agent方式无侵入实现
上面的代码中我们使用了 TransmittableThreadLocal,然后在提交Runnable任务时我们需要通过 TtlRunnable.get(runnable) 来修饰Runnable。但是如果我们项目中的代码已经写好了,如果要修改成本很大,此时就可以通过Agent挂载的方式来动态修改Runnable类
方法:启动时配置 javaagent
java -javaagent:C:\Users\gudian\Desktop\test\transmittable-thread-local-2.14.2.jar
-jar my.jar