网上有些文章说的 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();
}
}
}
}
}