ThreadPoolTaskExecutor & 上下文传递 & ThreadLocal

一、应用场景

有些异步线程场景,需要我们获取主线程对应的上下文信息,否则无法进行逻辑处理

1、MDC  --设置请求链路追踪的traceID,子线程知道可以进行链路追踪。

2、Request Headers 设置的特殊标识信息,存放在 TransmittableThreadLocal中,子线程需要知道相关信息。

二、任务装饰器 TaskDecorator

通过设置装饰器(线程装饰器),获取主线程的相关上下文,然后在子线程中进行设置。

优点: 所有异步调用的地方,均可获取到主线程的上下文,并进行统一传递和销毁,减少代码冗余。


@Configuration
@Slf4j
public class ThreadPoolConfig {

    @Bean("asyncTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(4);
        // 设置最大线程数
        executor.setMaxPoolSize(8);
        // 设置队列容量
        executor.setQueueCapacity(20);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("async");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象 的销毁。
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        executor.setAwaitTerminationSeconds(60);
        // 设置任务的装饰
        executor.setTaskDecorator(new ContextCopyDecorator());
        executor.initialize();
        return executor;
    }

    /**
     * 线程池任务-上下文拷贝 装饰器
     */
    static class ContextCopyDecorator implements TaskDecorator {
        @Nonnull
        @Override
        public Runnable decorate(@Nonnull Runnable runnable) {
            RequestAttributes context = RequestContextHolder.currentRequestAttributes();
            Map<String, Object> threadLocalMap = SecurityContextHolder.getLocalMap();
            Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();
            return () -> {
                try {
                    RequestContextHolder.setRequestAttributes(context);
                    SecurityContextHolder.setLocalMap(threadLocalMap);
                    if(Objects.nonNull(mdcContextMap)){
                        MDC.setContextMap(mdcContextMap);
                    }
                    runnable.run();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                    SecurityContextHolder.remove();
                    MDC.clear();
                }
            };
        }
    }
}

三、解释

ttl : 主要存储当前请求头相关上下文信息

public class SecurityContextHolder
{
    private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();

    public static void set(String key, Object value)
    {
        Map<String, Object> map = getLocalMap();
        map.put(key, value == null ? StringUtils.EMPTY : value);
    }

    public static String get(String key)
    {
        Map<String, Object> map = getLocalMap();
        return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));
    }

    public static <T> T get(String key, Class<T> clazz)
    {
        Map<String, Object> map = getLocalMap();
        return StringUtils.cast(map.getOrDefault(key, null));
    }

    public static Map<String, Object> getLocalMap()
    {
        Map<String, Object> map = THREAD_LOCAL.get();
        if (map == null)
        {
            map = new ConcurrentHashMap<String, Object>();
            THREAD_LOCAL.set(map);
        }
        return map;
    }

    public static void setLocalMap(Map<String, Object> threadLocalMap)
    {
        THREAD_LOCAL.set(threadLocalMap);
    }
}

MDC : 相关技术可以自行检索, 链路追踪,traceID

四、参考

多线程调用如何传递请求上下文?简述ThreadLocal和TaskDecorator_设置 mode_threadlocal-CSDN博客

五、解决了所有问题吗?NO NO

未完待分析

用这4招 优雅的实现Spring Boot 异步线程间数据传递_ttlexecutors.getttlexecutor-CSDN博客

threadlocal原理和线程间传递_threadlocal子线程怎么传递-CSDN博客

new 线程的时候,我们用完就进行了clear,当复用 -> 即从池子中再拿线程的时候,就没有上下文了。(暂没有解决)

TransmittableThreadLocal是由阿里开发的一个线程变量传递工具包,解决了InheritableThreadLocal只能再new Thread的时候传递本地变量,无法应用到线程池的问题

扩展知识

Spring安全策略可见性分为三个层级:

  1. MODE_THREADLOCAL 仅当前线程(默认)
  2. MODE_INHERITABLETHREADLOCAL 子线程可见
  3. MODE_GLOBAL 全局可见

可通过启动项参数进行设置

-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

参考资料

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一个线程局部变量,它的值只能被当前线程访问和修改,其他线程无法访问。使用ThreadLocal可以很方便地在多线程环境下保持上下文的一致性。 下面是一个使用ThreadLocal实现上下文的示例代码: ```java public class Context { private static final ThreadLocal<String> context = new ThreadLocal<>(); public static void setContext(String value) { context.set(value); } public static String getContext() { return context.get(); } public static void clearContext() { context.remove(); } } ``` 在这个上下文类中,我们使用了一个ThreadLocal变量来存储上下文信息。setContext方法用于设置当前线程的上下文信息,getContext方法用于获取当前线程的上下文信息,clearContext方法用于清除当前线程的上下文信息。 下面是一个使用上下文的示例代码: ```java public class Worker implements Runnable { public void run() { Context.setContext("Thread-" + Thread.currentThread().getId()); // 执行任务 String context = Context.getContext(); System.out.println("Task executed in context: " + context); Context.clearContext(); } } public class Main { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(new Worker()); } executorService.shutdown(); } } ``` 在这个示例中,我们创建了一个Worker类来执行任务。在Worker的run方法中,首先使用Context.setContext方法设置当前线程的上下文信息,然后执行任务,并使用Context.getContext方法获取当前线程的上下文信息,最后使用Context.clearContext方法清除当前线程的上下文信息。在Main类中,我们创建了一个线程池并提交了10个Worker任务,这些任务会在不同的线程中执行。当每个任务执行时,它会在自己的上下文中执行,保持了上下文的一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值