Spring应用-4-SpringBoot多线程处理数据

一. 前言

  1. 承接上文(不重要),获取到的数据需要进行分析,而数据量过大时,只依赖主线程会导致当前请求长时间锁定以至于超时,而且单个线程的处理效率太低,耗时超长。此时就需要用到多线程的操作异步处理数据。
  2. 分析场景:
    1. 多线程操作有两种:IO密集型和CPU密集型,不同的多线程操作做不同的线程数量配置,我这里的场景因为要大量读取数据库,故为IO密集型;
      • IO密集型:CPU逻辑核心数*2 或者 CPU逻辑核心数/(1-0.9)
      • CPU密集型:CPU逻辑核心数 + 1 或者 CPU逻辑核心数*2
      • 最佳配置:((线程IO等待时间 + 线程CPU时间) / 线程CPU时间) * CPU逻辑核心数
  3. 问题:
    1. 虽然创建了最大线程数量的任务类,但是同时执行任务的线程数量只有设定的核心线程的数量,这是因为LinkedBlockingDeque无界的任务队列的特性,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize设置的数量,当线程池的线程数达到corePoolSize后,就不会再增加了。
    2. 因为每个任务执行的时间并不稳定,如果平均分配任务,有可能执行快的线程提前完成任务后开始空闲。此时如果使用while()加小粒度分配任务数量的方式来多线程执行,执行完毕任务的线程会到队列中检查是否还有没有执行的任务,这样就更高效了。
  4. 扩展阅读

二. 示例

  1. application.yml配置(非必须,只是后面改起来方便)
    mine:
      thread:
        test:
          core-pool-size: 8
          max-pool-size: 16
    
  2. 线程池配置映射类(搭配1用的,不用的话可以不配)
    @Data
    @Component
    @ConfigurationProperties(prefix = "mine.thread.test")
    public class TestPoolCnfProperties {
        private Integer corePoolSize;
        private Integer maxPoolSize;
    }
    
  3. 业务类
    @Service
    public class TestService {
        @Resource
        private TestPoolCnfProperties poolCnf;
    
        @Resource
        private TestMapper mapper;
    
        public void runThread() {
            // 准备数据
            List<String> idList = mapper.getIds();
    
            // 创建线程池
            ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(poolCnf.getCorePoolSize(), poolCnf.getMaxPoolSize(),
                    60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
            // 均分方式分配任务:对于每个任务执行时间差不多情况下,效率比较高
            int avg = idList.size() / poolCnf.getMaxPoolSize();
            avg = avg != 0 ? avg : 1;
            // 计算需要创建多少线程,如果任务数量不够线程数量,不需要全创建
            int maxThreadSize = idList.size() >= poolCnf.getMaxPoolSize() ? poolCnf.getMaxPoolSize() : idList.size();
            // 循环分配任务并启动子线程
            for (int i = 0; i < maxThreadSize; i++) {
                TestRunnable testRunnable;
                if (i + 1 < maxThreadSize) {
                    // 这里把this传入是因为所有的数据库操作和数据处理操作逻辑都放在当前类中
                    testRunnable = new TestRunnable(this, idList.subList(i * avg, (i + 1) * avg));
                } else {
                    // 由最后一个线程执行所有剩余的任务
                    testRunnable = new TestRunnable(this, idList.subList(i * avg, idList.size()));
                }
                // 启动
                poolExecutor.submit(testRunnable);
            }
        }
    }
    
  4. 线程执行类
    public class TestRunnable implement Runnable {
        private TestService service;
    
        private List<String> idList;
    
        public TestRunnable(TestService service, List<String> idList) {
            this.service = service;
            this.idList = idList;
        }
    
        @Override
        public void run() {
            // 注意,子线程无法向上抛出异常,需要内部处理,如果不处理,子线程会中断并处于等待状态
            try {
                // service.disposeXXX();
            } catch (Exception e) {
                System.out.println(e.toString());
            }
        }
    }
    
posted @ 2022-09-14 17:46  苍凉温暖  阅读(2680)  评论(0编辑  收藏  举报