一、应用场景
有些异步线程场景,需要我们获取主线程对应的上下文信息,否则无法进行逻辑处理
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安全策略可见性分为三个层级:
- MODE_THREADLOCAL 仅当前线程(默认)
- MODE_INHERITABLETHREADLOCAL 子线程可见
- MODE_GLOBAL 全局可见
可通过启动项参数进行设置
-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL