解决@Async导致ThreadLocal值丢失问题

网上有些文章说的 InheritableThreadLocal  这个只是在创建 子线程的时候才会对子线程Thread Local有效,

问题:spring中使用@Async 处理任务,这个时候开了线程池,线程池中的线程不一定会立即被销毁,那么InheritableThreadLocal 中无法拿到父线程中的新值。

所以重写 线程池,@Async  注解开了线程池,执行任务时,调用了 execute 或者 submit方法,我们把这两类重写了,提交前是当前线程,获取 threadLocal, 提交后,比如submit方法肯定会执行 callable 里面的方法体, 这个时候把之前获取的参数拿来,设置到现在子线程的threadLocal,问题就解决了,目前我自己的应用场景是没什么问题。

别忘了任务结束后清理数据。(清理数据时,存在一个问题:当拒绝策略是CallerRunsPolicy时,执行任务的线程是父线程,这个时候清理数据,会直接把父线程的threadLocal清理,极有可能导致后续业务出错,所以我们需要判断一下,当前执行任务的线程是不是和父线程相等,如果是的话,就没必要执行移除threadlocal,让父线程的逻辑代码自行移除)

如果只是针对 @Async 注解使用,下面代码中,没必要重写execute方法,直接重写submit就行。

同理,子线程其他信息,比如请求头什么的,也可以用类似的办法来处理,如果不是@Async,重写jdk本身的线程池就行,这里是用spring的ThreadPoolTaskExecutor 举例

代码如下:

使用时,指定@Async 注解 使用我们自定义的线程池就行(代码中最好自定义线程池,以前排查过一个bug,在任务耗时比较长,并发不是很大的情况下,线程超过了1500个,直接收到了报警,排查发现,绝大多数都是 spring 注解 @Async 默认生成的线程,最后我们做了全局配置,自定义线程池各个参数,才控制住,具体办法百度吧,)

@Async(AsyncThreadPoolConfig.COMMON_THREAD_POOL_NAME)
@Configuration
public class AsyncThreadPoolConfig {

    /**
     * 用于异步调用feign
     */
    public final static String COMMON_THREAD_POOL_NAME = "COMMON_THREAD_POOL_NAME";

    //比如这里存了登录用户-信息, 举个例子而已
    public static ThreadLocal<Object> TEST = new ThreadLocal<>();

    /**
     * 通用线程池
     * @param
     * @Author  yq
     * @Date   2022/6/23 14:30
     * @Return org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
     */
    @Bean(name = COMMON_THREAD_POOL_NAME)
    public ThreadPoolTaskExecutor tpmCommonThreadPool() {
        ThreadPoolTaskExecutor pool = new CustomThreadPoolTaskExecutor();
        pool.setCorePoolSize(1);
        pool.setMaxPoolSize(200);
        pool.setThreadNamePrefix("呵呵哒");
        pool.setWaitForTasksToCompleteOnShutdown(true);
        pool.setQueueCapacity(10);
        pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        return pool;
    }

    private static class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
        @Override
        public void execute(Runnable task) {
            ConcurrentHashMap<String, Object> loginMap = ThreadLocalUtil.get();
            super.execute(new Runner(task, loginMap, Thread.currentThread()));
        }

        @Override
        public Future<?> submit(Runnable task) {
            Object loginMap = TEST.get();
            return super.submit(new Runner(task, loginMap, Thread.currentThread()));
        }

        @Override
        public <T> Future<T> submit(Callable<T> task) {
            Object loginMap = TEST.get();
            return super.submit(new Caller(task, loginMap, Thread.currentThread()));
        }
    }

    @Slf4j
    private static class Runner implements Runnable {
        private Thread parentThread;
        
        private Runnable runnable;

        private Object loginMap;

        public Runner(Runnable runnable, Object loginMap, Thread parentThread) {
            this.runnable = runnable;
            this.loginMap = loginMap;
            this.parentThread = parentThread;
        }

        @Override
        public void run() {
            //这里是子线程
            TEST.set(loginMap);
            try {
                runnable.run();
            } catch (Exception e) {
                log.error("异步任务异常 {}", e.getMessage());
                throw e;
            } finally {
                if (parentThread != Thread.currentThread()) {
                    TEST.remove();
                }
            }
        }
    }


    @Slf4j
    private static class Caller implements Callable {
        private Thread parentThread;

        private Callable runnable;

        private Object loginMap;

        public Caller(Callable runnable, Object loginMap, Thread parentThread) {
            this.runnable = runnable;
            this.loginMap = loginMap;
            this.parentThread = parentThread;
        }

        @Override
        public Object call() throws Exception {
            //这里是子线程
            TEST.set(loginMap);
            try {
                return runnable.call();
            } catch (Exception e) {
                log.error("异步任务异常 {}", e.getMessage());
                throw e;
            } finally {
                if (parentThread != Thread.currentThread()) {
                    TEST.remove();
                }
            }
        }

    }
}

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值