Spring异步线程池-TaskDecorator传递线程上下文

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,进行装饰;

posted on 2023-10-25 16:00  mzone  阅读(361)  评论(0编辑  收藏  举报