本篇文章写于2021年的中秋节,在节前,我的一个上线功能出现的性能方面的问题,问题是这样的:20个单据信息分表分布在20个表中,有一个功能是查询我的所有单据,当时,也没考虑太多,就使用switch-case到20个表中查询然后程序里聚合。直到线上出现用户使用卡死现象的出现,我才意识到自己的错误是多么的愚蠢。一个接口查询20个表,一定是超时的。
解决思路
在老哥们的帮助下,替我想到了几个相对好的补救措施
- 开多线程查询:开20个线程查询,这样本来是20*单次查询的时间,现在就是单次最长查询时间
20张表汇集为一张表:这个解决策略,我当时的第一反应时,为啥当时没有想到。可是后来仔细回顾,当时做这块功能的时候,并不知道到底需要哪些字段,且这边没有使用到Canal等工具,这样只能在生成单据时插入信息,这样影响地方太大
方案选择
就开多线程的方案,这边进行一下练习,自然是分配20个
Callable
然后丢入线程池中,接收到List后聚集到一个List中进行返回。
设计到的知识:Callable的使用
- 线程池的使用
控制任务组:20个任务这是一个组,如何让先查询出结果的数据先加入List。这里需要了解一下:
ExecutorCompletionService
Callable的使用
Callable定义一个可以有返回值的任务
- Future可以被交给某个线程,异步获取线程任务执行的结果
综合上面两点,于是,FutureTask出现了,FutureTask可以将Callable转换为Runnable和Future。具体代码:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable =()->{
System.out.println("执行callable");
return 1;
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
Integer integer = futureTask.get();
System.out.println(integer);
}
线程池的使用
线程池的创建要记住Executors,通过它可以创建多种线程池。线程池创建好后就可以向池中丢任务,可以是Runnable也可以是Callable
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Callable<Integer> callable = ()->1;
Future<Integer> future = executorService.submit(callable);
Integer integer = future.get();
System.out.println(integer);
}
注:写上面代码的时候,阿里插件提示,不要使用Excutors,要使用ThreadPoolExecutor创建,等我忙完这一阵子就去看一下如何自定义一个线程池。
使用线程操作的流程是:
- 创建一个线程池
- 提交Callable任务或Runnable任务
- 如果是一个Callable任务,接收Future结果对象
当不提交任何任务时,shutdown(注意是shutdown)
控制任务组
以我要解决的问题为例子,查询任一种单据都是一个任务,这里我会创建20个任务(Callable),这个20个任务就是一个任务组,任务全部完成才可以整个接口功能的完成。于是我用简单的代码模拟一下解决思路。
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Callable<Integer>> task = new ArrayList<Callable<Integer>>();
for (int i = 0; i < 20; i++) {
int finalI = i;
Callable<Integer> callable = () -> {
if(finalI%2==0&&finalI!=0) {
Thread.sleep(1000*finalI);
}
return finalI;
};
task.add(callable);
}
List<Future<Integer>> futures = executorService.invokeAll(task);
for(Future<Integer> future :futures){
System.out.println(future.get());
}
}
这样的话,所有的任务都要等到其他任务都完成才可以出结果。于是有的先完成的任务就无法先处理。
于是,可以采用如下方式来进行编写public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorCompletionService<Integer> integerExecutorCompletionService = new ExecutorCompletionService<>(executorService);
for (int i = 0; i < 20; i++) {
int finalI = i;
Callable<Integer> callable = () -> {
if(finalI%2==0&&finalI!=0) {
Thread.sleep(1000*finalI);
}
return finalI;
};
integerExecutorCompletionService.submit(callable);
}
for(int i=0;i<20;i++){
Integer integer = integerExecutorCompletionService.take().get();
System.out.println(integer);
}
}
总结
上面简单的练习了多线程的使用,目的是解决项目的一个接口串行查询20个sql,时间慢的问题。
最后,祝大家中秋快乐。