今天写需求,调同事的接口,同事的接口里需要从RequestContext拿一些参数用来查询,我一开始还能查到呢,后边自己“优化”了一下代码,哎嗨?RequestContext里咋空了,啥也没了,我的HTTPRequestHolder对象去哪了?
这就开始debug了。
由于之前重写的那部分代码为了提高性能,使用了CompletableFuture异步编程,而我“优化”代码的时候,为了减少查询次数,把调用同事接口的代码写进了之前的CompletableFuture代码中。也就是在这个时候,我的HTTPRequest对象就没了,那他是怎么没的呢?
让我们先看看RequestContextHolder.getRequestAttributes的底层实现:
1 |
|
关键就在这个requestAttributesHolder对象:
1 | private static final ThreadLocal<RequestAttributes> requestAttributesHolder = |
它是一个 ThreadLocal 对象,当我们在多线程代码中使用它时,HTTPRequestHolder对象是主线程的私有变量,在其他创建的子线程中,ThreadLocal 对象并不会被共享,自然其他线程就拿不到HTTPRequestHolder对象了。
那该怎么解决这个问题呢?
有两种方法:
将RequestContext中的参数传递给被创建的子线程中。
这种办法非常简单,在接口里多加个形参就好了,我本来不太喜欢这种暴力的方式,但我的mentor告诉我:在我现在的场景下,我就应该这么做,因为请求上下文只在主线程中才有意义,子线程中要请求上下文干嘛呢?你就应该把要用的参数直接传递给子线程
将主线程中使用的上下文,复制到被创建的子线程中。
这是我比较想用的办法,虽然经过mentor的指点,这个办法不太适合,但我还是想探究一下怎么搞:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class XxPoolConfig {
/**
* 获取当前系统的CPU 数目
*/
static int cpuNums = Runtime.getRuntime().availableProcessors();
/**
* 默认线程池
*
* @return Executor
*/
public ThreadPoolTaskExecutor threadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:线程池创建时候初始化的线程数
if(cpuNums==1) {
executor.setCorePoolSize(5);
}else {
executor.setCorePoolSize(10);
}
// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(cpuNums * 5);
//队列中最大的数目
executor.setQueueCapacity(16);
//线程名称前缀
executor.setThreadNamePrefix("CalenderThreadPool_");
//rejection-policy:当pool已经达到max size的时候,如何处理新任务
//CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
//对拒绝task的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//线程空闲后的最大存活时间
executor.setKeepAliveSeconds(60);
// 设置线程装饰器
executor.setTaskDecorator(new ContextDecorator());
//加载
executor.initialize();
return executor;
}
}关键就在:
executor.setTaskDecorator(new ContextDecorator());
,给线程设置一个装饰器,在装饰器的具体实现类中进行复制:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ContextDecorator implements TaskDecorator {
public Runnable decorate(Runnable runnable) {
// 获取主线程中的上下文
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
return () -> {
try {
// 将上下文复制到新的线程中
RequestContextHolder.setRequestAttributes(context, true);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
}
}
下篇文章具体研究一下ThreadLocal。