一,概述
对于没有关联可以同时请求的接口或者业务逻辑,建议使用线程池进行并发的访问,缩短请求的时间。
在平时的开发和业务中,有时候我们需要处理接口返回慢的问题,或者我们想要封装一些数据,比如说返回的oss的url,再返回的列表中的数据每一个数据返回的url都是不尽相同的,但是又是没有任何关联的,所以此时我们就需要使用并发的去查询和封装这个数据,不能同步的去执行了,不然的话可能会影响到接口的反应时间,还有我们在业务中,需要查询多方的数据进行合并到一起的时候,我们可以使用线程池并发查询各个业务逻辑然后组装都一起,这样就可以缩短响应的时间,是一个解决反应时间的一个手段,当然有时候也可以使用其他手段,比如es,或者缓存这样的,只要可以满足业务逻辑和用户响应都是可以的。下面,我们就将使用线程池并发获取数据的代码以及如何去做,做一下展示。
二,准备线程池
定义线程池,主要的参数就是定义好核心线程池,最大线程池,以及拒绝策略,若不懂线程池的参数的可以先去复习一下线程池的几个参数得的配置,定义的原则就是根据业务的需要,进行合理的处理
代码层面
@Configuration
@EnableAsync
public class ExecutorConfig {
@Value("${executor.thread.core.pool.size:10}")
private int corePoolSize;
@Value("${executor.thread.max.pool.size:40}")
private int maxPoolSize;
@Value("${executor.thread.queue.capacity:100}")
private int queueCapacity;
@Value("${executor.thread.keep.alive:10}")
private int keepAlive;
@Value("${executor.thread.name.prefix:common-pool}")
private String namePrefix;
@Bean(Constants.THREAD_POOL_BEAN_NAME)
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//设置核心线程数
executor.setCorePoolSize(corePoolSize);
//设置最大线程数
executor.setMaxPoolSize(maxPoolSize);
// 配置队列大小,视业务而定
executor.setQueueCapacity(queueCapacity);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(keepAlive);
//核心线程允许超时
executor.setAllowCoreThreadTimeOut(true);
//设置默认线程名称
executor.setThreadNamePrefix(namePrefix);
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//加载
executor.initialize();
return executor;
}
三,异步逻辑代码
主要是业务逻辑的返回值要是Future,加上@Async注解,具体的业务逻辑就可以放在AsyncResult里面了,这是定义异步执行的逻辑
@Component
public class AsyncTestService {
@Async(Constants.THREAD_POOL_BEAN_NAME) //线程池的名称
Future<String> testAsync(Integer integer) {
//执行具体的业务逻辑
return new AsyncResult<>(queryData(integer));
}
public String queryData(Integer integer) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "第" + integer + "次";
}
}
业务逻辑调用并发并处理
一个是异步执行,一个是同步执行,异步代码中的逻辑,主要就是定一个Future的list,然后先存放起来,在完成后,我们就遍历future,然后调用get().方法, 此时这个方法就是阻塞的,然后封装结果,不是很复杂,没有多少的代码处理
@Component
public class TestServiceImpl implements TestService {
@Autowired
private AsyncTestService asyncTestService;
@Override
public String asyncQuery() {
long start = System.currentTimeMillis();
Map<String, String> map = new HashMap<>();
List<Future<String>> result = new ArrayList<>();
for (int i = 1; i < 1000; i++) {
Future<String> stringFuture = asyncTestService.testAsync(i);
result.add(stringFuture);
}
System.out.println("并发调用后时间:" + (System.currentTimeMillis() - start));
for (Future<String> future : result) {
try {
String s = future.get();
map.put(s, s);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
System.out.println("调用完成后时间:" + (System.currentTimeMillis() - start));
return JSON.toJSONString(map);
}
@Override
public String syncQuery() {
long start = System.currentTimeMillis();
Map<String, String> map = new HashMap<>();
List<String> result = new ArrayList<>();
for (int i = 1; i < 1000; i++) {
String stringFuture = asyncTestService.queryData(i);
result.add(stringFuture);
}
System.out.println("并发调用后时间:" + (System.currentTimeMillis() - start));
for (String future : result) {
map.put(future, future);
}
System.out.println("调用完成后时间:" + (System.currentTimeMillis() - start));
return JSON.toJSONString(map);
}
}
四,异步和同步响应时间比较
异步执行
同步执行
总结
可以看出来,响应时间真的是差别很明显的,所以需要并发查询的,或者循环处理数据的,建议使用线程池并发执行