1. 本篇文章写于2021年的中秋节,在节前,我的一个上线功能出现的性能方面的问题,问题是这样的:20个单据信息分表分布在20个表中,有一个功能是查询我的所有单据,当时,也没考虑太多,就使用switch-case20个表中查询然后程序里聚合。直到线上出现用户使用卡死现象的出现,我才意识到自己的错误是多么的愚蠢。一个接口查询20个表,一定是超时的。

解决思路

在老哥们的帮助下,替我想到了几个相对好的补救措施

  • 开多线程查询:开20个线程查询,这样本来是20*单次查询的时间,现在就是单次最长查询时间
  • 20张表汇集为一张表:这个解决策略,我当时的第一反应时,为啥当时没有想到。可是后来仔细回顾,当时做这块功能的时候,并不知道到底需要哪些字段,且这边没有使用到Canal等工具,这样只能在生成单据时插入信息,这样影响地方太大

    方案选择

    就开多线程的方案,这边进行一下练习,自然是分配20个Callable然后丢入线程池中,接收到List后聚集到一个List中进行返回。
    设计到的知识:

  • Callable的使用

  • 线程池的使用
  • 控制任务组:20个任务这是一个组,如何让先查询出结果的数据先加入List。这里需要了解一下:ExecutorCompletionService

    Callable的使用

  • Callable定义一个可以有返回值的任务

  • Future可以被交给某个线程,异步获取线程任务执行的结果

综合上面两点,于是,FutureTask出现了,FutureTask可以将Callable转换为Runnable和Future。具体代码:

  1. public static void main(String[] args) throws ExecutionException, InterruptedException {
  2. Callable<Integer> callable =()->{
  3. System.out.println("执行callable");
  4. return 1;
  5. };
  6. FutureTask<Integer> futureTask = new FutureTask<>(callable);
  7. Thread thread = new Thread(futureTask);
  8. thread.start();
  9. Integer integer = futureTask.get();
  10. System.out.println(integer);
  11. }

线程池的使用

线程池的创建要记住Executors,通过它可以创建多种线程池。线程池创建好后就可以向池中丢任务,可以是Runnable也可以是Callable

  1. public static void main(String[] args) throws ExecutionException, InterruptedException {
  2. ExecutorService executorService = Executors.newCachedThreadPool();
  3. Callable<Integer> callable = ()->1;
  4. Future<Integer> future = executorService.submit(callable);
  5. Integer integer = future.get();
  6. System.out.println(integer);
  7. }
  1. 注:写上面代码的时候,阿里插件提示,不要使用Excutors,要使用ThreadPoolExecutor创建,等我忙完这一阵子就去看一下如何自定义一个线程池。

使用线程操作的流程是:

  • 创建一个线程池
  • 提交Callable任务或Runnable任务
  • 如果是一个Callable任务,接收Future结果对象
  • 当不提交任何任务时,shutdown(注意是shutdown)

    控制任务组

    以我要解决的问题为例子,查询任一种单据都是一个任务,这里我会创建20个任务(Callable),这个20个任务就是一个任务组,任务全部完成才可以整个接口功能的完成。于是我用简单的代码模拟一下解决思路。

    1. public static void main(String[] args) throws ExecutionException, InterruptedException {
    2. ExecutorService executorService = Executors.newCachedThreadPool();
    3. List<Callable<Integer>> task = new ArrayList<Callable<Integer>>();
    4. for (int i = 0; i < 20; i++) {
    5. int finalI = i;
    6. Callable<Integer> callable = () -> {
    7. if(finalI%2==0&&finalI!=0) {
    8. Thread.sleep(1000*finalI);
    9. }
    10. return finalI;
    11. };
    12. task.add(callable);
    13. }
    14. List<Future<Integer>> futures = executorService.invokeAll(task);
    15. for(Future<Integer> future :futures){
    16. System.out.println(future.get());
    17. }
    18. }

    这样的话,所有的任务都要等到其他任务都完成才可以出结果。于是有的先完成的任务就无法先处理。
    于是,可以采用如下方式来进行编写

    1. public static void main(String[] args) throws InterruptedException, ExecutionException {
    2. ExecutorService executorService = Executors.newCachedThreadPool();
    3. ExecutorCompletionService<Integer> integerExecutorCompletionService = new ExecutorCompletionService<>(executorService);
    4. for (int i = 0; i < 20; i++) {
    5. int finalI = i;
    6. Callable<Integer> callable = () -> {
    7. if(finalI%2==0&&finalI!=0) {
    8. Thread.sleep(1000*finalI);
    9. }
    10. return finalI;
    11. };
    12. integerExecutorCompletionService.submit(callable);
    13. }
    14. for(int i=0;i<20;i++){
    15. Integer integer = integerExecutorCompletionService.take().get();
    16. System.out.println(integer);
    17. }
    18. }

    总结

    上面简单的练习了多线程的使用,目的是解决项目的一个接口串行查询20个sql,时间慢的问题。
    最后,祝大家中秋快乐。