TaskDecorator:
TaskDecorator是一个执行回调方法的装饰器,主要应用于线程间传递数据,或者提供任务的监控/统计信息。
从主线程拷贝数据到子线程,具体数据实际上是封装到threadlocal里面。
实现方式:
定义一个TaskDecorator,在线程池中设置使用这个TaskDecorator。
注意线程池中有的线程是一直存在一直被复用的,所以线程执行完成后需要在TaskDecorator的finally方法中移除传递的上下文对象,否则就存在内存泄漏问题。
定义TaskDecorator实例:
继承TaskDecorator接口创建ContextCopyingDecorator 实现类,重写decorate方法,设置需要传递的上下文和变量值。注意finally代码块。
public class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
try {
RequestAttributes context = RequestContextHolder.currentRequestAttributes(); //1
Map<String,String> previous = MDC.getCopyOfContextMap(); //2
SecurityContext securityContext = SecurityContextHolder.getContext(); //3
return () -> {
try {
RequestContextHolder.setRequestAttributes(context); //1
MDC.setContextMap(previous); //2
SecurityContextHolder.setContext(securityContext); //3
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes(); // 1
MDC.clear(); // 2
SecurityContextHolder.clearContext(); // 3
}
};
} catch (IllegalStateException e) {
return runnable;
}
}
}
这里使用了slf4j-api的jar包里面的一个MDC的类。通过查看源码发现,该类实际上是封装了threadlocal。子线程runnable执行前,通过静态方法getCopyOfContextMap获取主线程里面的数据,在子线程里面通过setContextMap方法设置到子线程的threadlocal里面。这样子线程就获取到主线程的数据了。
线程池使用TaskDecorator:
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("MyExecutor-");
// for passing in request scope context
executor.setTaskDecorator(new ContextCopyingDecorator());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
涉及知识点
ThreadLocal,InheritableThreadLocal,TaskDecorator,RequestContextHolder,TransmittableThreadLocal(通过继承InheritableThreadLocal实现)
测试 ThreadLocal,InheritableThreadLocal,TransmittableThreadLocal的区别和使用
1.父线程使用ThreadLocal,子线程创建时不会拥有父类的threadLocal信息
2.父线程使用InheritableThreadLocal,子线程创建时,默认init方法会拿到父类的InheritableThreadLocal信息,这种在线程池/线程复用的情况下,由于init方法只会在初始化时获取父线程的数据,复用的时候也没法再从父线程那里新的InheritableThreadLocal的数据,此种情况下继续使用,很容易出bug(InheritableThreadLocal适用于非线程池和复用线程,单独创建销毁子线程执行的情况)
3.父线程使用TransmittableThreadLocal,子线程创建时拥有父类的TransmittableThreadLocal信息,在线程池/线程复用的情况下不会出现读取到脏数据的情况
总结
- 在异步线程池的情况下,通过ThreadLocal+TaskDecorator一般即可解决遇到的透传问题;
- TransmittableThreadLocal,其原理也是对Runnable,Callable,进行装饰;