项目背景:
在某次项目中,需要对用户的评分进行计算,评分分5个大项,每个细项又分8个大项,每个细项的数据都来源于另外一个数据查询服务,数据查询服务的接口平均响应时间大概是100ms,查询到数据后还有进行加工处理,评分的要求是要再2s内完成,如果使用常规的顺序执行,时间至少是5 * 8 * 0.1 = 4s,也就是评分接口响应时间超过4s。
问题分析:
数据的调用接口之间没有依赖关系,因此可以使用多线程来调用数据接口,等所有数据返回后,再统一加工处理,所以问题就变成以下处理点:
- 如果启用多个线程去调用数据结构
- 如何统一获取返回值,进行数据加工
显然,可以使用线程池技术。我们项目使用的是springboot框架,因此选择使用spring的
ThreadPoolTaskExecutor 作为线程池的创建器。
示例:
- 首先,配置一个线程池
在线程池的配置过程中,线程池的执行规则是,当接受到任务后,如果核心线程的数量未到设置值,会创建新线程去执行,如果线程已经到达设置值,则会将任务存在队列中,如果队列满了且线程未到达最大线程设置,则又会创建新线程去执行,如果线程队列和最大值都满了,则需要设置拒绝策略,具体可以自行百度。
在这里,有个明细的问题,有些订餐系统中也会遇到,就是请求是爆发式的,比方说,在平时,请求可能就三两个,但是在特定的时间点,会迅速增加到几百、几千甚至更高,这样的情况下,如果核心线程数量设置的很低,那么就面临两个选择,第一是队列小,系统可以很快的提高到最大先线程,但是如果最大线程也处理不过来,就会出现任务被拒绝。第二是队列大,那就会出现很难到队列满,导致一直只有核心线程在执行,效率和性能下降。如果核心线程设置的很高,队列小,同样有请求被拒绝的风险,队列大,依然只有核心线程在执行,但是由于核心线程设置的高,所以效率和性能是可以保证的,但是在平时请求只有三两个的时候,线程池浪费就严重。
JDK1.6引入了一个新参数 :核心线程允许超时 executor.setAllowCoreThreadTimeOut(true);
可以用来解决该问题,具体使用可以自行百度。
/**
* @describe: 线程池配置
* @author: sunlight
* @date: 2021/7/14 14:31
*/
@Configuration
@EnableAsync
public class ExecutorConfig {
/**
* 线程池配置
*
* @return
*/
@Bean("threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(40);
// 设置最大线程数
executor.setMaxPoolSize(40);
// 配置队列大小,缓存100个任务,视业务而定
executor.setQueueCapacity(100);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(10);
//核心线程允许超时
executor.setAllowCoreThreadTimeOut(true);
//设置默认线程名称
executor.setThreadNamePrefix("test");
//初始化
executor.initialize();
return executor;
}
}
- 其次,配置线程任务,等价于实现runable或者Thread的run方法
/**
* 测试
*/
@Async("threadPoolTaskExecutor")
public Future<String> test(int i, int j) {
String name = Thread.currentThread().getName();
log.info("当前线程,{}", name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<String>("test:" + i + j);
}
- 调用线程方法,捕获返回值
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
private TestService testService;
/**
* 执行返回值
*
* @return
*/
@RequestMapping("/test")
public String test() throws ExecutionException, InterruptedException {
long begin = System.currentTimeMillis();
List<Future<String>> futureList = new ArrayList<>(40);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 8; j++) {
Future<String> result = testService.test(i, j);
futureList.add(result);
}
}
//future.get()会引起阻塞,所以不能再上面for循环获取
for (Future<String> future : futureList) {
String s = future.get();
System.out.println("result" + s);
}
long end = System.currentTimeMillis();
return "ok" + (end - begin);
}
}
- 数据加工
结果遍历完之后,就可以进行数据加工了。