java异步线程之间数据传递
文章目录
- 1、手动设置
- 2、线程池设置TaskDecorator(推荐)
- 3、InheritableThreadLocal
- 4、TransmittableThreadLocal(推荐)
/**
* @description 用户上下文信息
*/
public class OauthContext {
private static final ThreadLocal<LoginVal> loginValThreadLocal=new ThreadLocal<>();
public static LoginVal get(){
return loginValThreadLocal.get();
}
public static void set(LoginVal loginVal){
loginValThreadLocal.set(loginVal);
}
public static void clear(){
loginValThreadLocal.remove();
}
}
1、手动设置
每执行一次异步线程都要分为两步:
- 获取父线程的LoginVal
- 将LoginVal设置到子线程,达到复用
public void handlerAsync() {
//1. 获取父线程的loginVal
LoginVal loginVal = OauthContext.get();
log.info("父线程的值:{}",OauthContext.get());
CompletableFuture.runAsync(()->{
//2. 设置子线程的值,复用
OauthContext.set(loginVal);
log.info("子线程的值:{}",OauthContext.get());
});
}
每次开异步线程都需要手动设置,重复代码太多,看了头疼
2、线程池设置TaskDecorator(推荐)
TaskDecorator这是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息。
TaskDecorator是一个接口,首先需要去实现它,代码如下:
/**
* @description 上下文装饰器
*/
public class ContextTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
//获取父线程的loginVal
LoginVal loginVal = OauthContext.get();
return () -> {
try {
// 将主线程的请求信息,设置到子线程中
OauthContext.set(loginVal);
// 执行子线程,这一步不要忘了
runnable.run();
} finally {
// 线程结束,清空这些信息,否则可能造成内存泄漏
OauthContext.clear();
}
};
}
}
这里只是设置了LoginVal,实际开发中其他的共享数据,比如SecurityContext,RequestAttributes…
TaskDecorator需要结合线程池使用,实际开发中异步线程建议使用线程池,只需要在对应的线程池配置一下,代码如下:
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
poolTaskExecutor.setCorePoolSize(xx);
poolTaskExecutor.setMaxPoolSize(xx);
// 设置线程活跃时间(秒)
poolTaskExecutor.setKeepAliveSeconds(xx);
// 设置队列容量
poolTaskExecutor.setQueueCapacity(xx);
//设置TaskDecorator,用于解决父子线程间的数据复用
poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
return poolTaskExecutor;
}
此时业务代码就不需要去设置子线程的值,直接使用即可,代码如下:
public void handlerAsync() {
log.info("父线程的用户信息:{}", OauthContext.get());
//执行异步任务,需要指定的线程池
CompletableFuture.runAsync(()-> log.info("子线程的用户信息:{}", OauthContext.get()),taskExecutor);
}
CompletableFuture
执行异步任务,使用@Async
这个注解同样是可行的
3、InheritableThreadLocal
这种方案不建议使用,InheritableThreadLocal虽然能够实现父子线程间的复用,但是在线程池中使用会存在复用的问题
这种方案使用也是非常简单,直接用InheritableThreadLocal替换ThreadLocal即可,代码如下:
/**
* @description 用户上下文信息
*/
public class OauthContext {
private static final InheritableThreadLocal<LoginVal> loginValThreadLocal=new InheritableThreadLocal<>();
public static LoginVal get(){
return loginValThreadLocal.get();
}
public static void set(LoginVal loginVal){
loginValThreadLocal.set(loginVal);
}
public static void clear(){
loginValThreadLocal.remove();
}
}
4、TransmittableThreadLocal(推荐)
TransmittableThreadLocal是阿里开源的工具,弥补了InheritableThreadLocal的缺陷,在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。
使用起来也是非常简单,添加依赖如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
OauthContext改造代码如下:
/**
* @description 用户上下文信息
*/
public class OauthContext {
private static final TransmittableThreadLocal<LoginVal> loginValThreadLocal=new TransmittableThreadLocal<>();
public static LoginVal get(){
return loginValThreadLocal.get();
}
public static void set(LoginVal loginVal){
loginValThreadLocal.set(loginVal);
}
public static void clear(){
loginValThreadLocal.remove();
}
}